@@ -5,6 +5,7 @@ import ox.*
5
5
import java .util .concurrent .{CountDownLatch , Semaphore }
6
6
import scala .collection .{IterableOnce , mutable }
7
7
import scala .concurrent .duration .FiniteDuration
8
+ import scala .util .Try
8
9
9
10
trait SourceOps [+ T ] { this : Source [T ] =>
10
11
// view ops (lazy)
@@ -55,6 +56,21 @@ trait SourceOps[+T] { this: Source[T] =>
55
56
def intersperse [U >: T ](inject : U )(using Ox , StageCapacity ): Source [U ] =
56
57
intersperse(None , inject, None )
57
58
59
+ private def intersperse [U >: T ](start : Option [U ], inject : U , end : Option [U ])(using Ox , StageCapacity ): Source [U ] =
60
+ val c = StageCapacity .newChannel[U ]
61
+ forkDaemon {
62
+ start.foreach(c.send)
63
+ var firstEmitted = false
64
+ repeatWhile {
65
+ receive() match
66
+ case ChannelClosed .Done => end.foreach(c.send); c.done(); false
67
+ case ChannelClosed .Error (e) => c.error(e); false
68
+ case v : U @ unchecked if ! firstEmitted => firstEmitted = true ; c.send(v); true
69
+ case v : U @ unchecked => c.send(inject); c.send(v); true
70
+ }
71
+ }
72
+ c
73
+
58
74
/** Intersperses this source with start, end and provided elements and forwards it to the returned channel.
59
75
*
60
76
* @param start
@@ -80,21 +96,6 @@ trait SourceOps[+T] { this: Source[T] =>
80
96
def intersperse [U >: T ](start : U , inject : U , end : U )(using Ox , StageCapacity ): Source [U ] =
81
97
intersperse(Some (start), inject, Some (end))
82
98
83
- private def intersperse [U >: T ](start : Option [U ], inject : U , end : Option [U ])(using Ox , StageCapacity ): Source [U ] =
84
- val c = StageCapacity .newChannel[U ]
85
- forkDaemon {
86
- start.foreach(c.send)
87
- var firstEmitted = false
88
- repeatWhile {
89
- receive() match
90
- case ChannelClosed .Done => end.foreach(c.send); c.done(); false
91
- case ChannelClosed .Error (e) => c.error(e); false
92
- case v : U @ unchecked if ! firstEmitted => firstEmitted = true ; c.send(v); true
93
- case v : U @ unchecked => c.send(inject); c.send(v); true
94
- }
95
- }
96
- c
97
-
98
99
/** Applies the given mapping function `f` to each element received from this source, and sends the results to the returned channel. At
99
100
* most `parallelism` invocations of `f` are run in parallel.
100
101
*
@@ -366,6 +367,15 @@ trait SourceOps[+T] { this: Source[T] =>
366
367
def interleave [U >: T ](other : Source [U ], segmentSize : Int = 1 , eagerComplete : Boolean = false )(using Ox , StageCapacity ): Source [U ] =
367
368
Source .interleaveAll(List (this , other), segmentSize, eagerComplete)
368
369
370
+ /** Accumulates all elements received from the channel into a list. Blocks until the channel is done.
371
+ * @throws ChannelClosedException
372
+ * when there is an upstream error.
373
+ */
374
+ def toList : List [T ] =
375
+ val b = List .newBuilder[T ]
376
+ foreach(b += _)
377
+ b.result()
378
+
369
379
/** Invokes the given function for each received element. Blocks until the channel is done.
370
380
* @throws ChannelClosedException
371
381
* when there is an upstream error.
@@ -378,15 +388,6 @@ trait SourceOps[+T] { this: Source[T] =>
378
388
case t : T @ unchecked => f(t); true
379
389
}
380
390
381
- /** Accumulates all elements received from the channel into a list. Blocks until the channel is done.
382
- * @throws ChannelClosedException
383
- * when there is an upstream error.
384
- */
385
- def toList : List [T ] =
386
- val b = List .newBuilder[T ]
387
- foreach(b += _)
388
- b.result()
389
-
390
391
/** Passes each received element from this channel to the given sink. Blocks until the channel is done.
391
392
* @throws ChannelClosedException
392
393
* when there is an upstream error, or when the sink is closed.
@@ -513,13 +514,62 @@ trait SourceOps[+T] { this: Source[T] =>
513
514
}
514
515
}
515
516
c
517
+
518
+ /** Returns the first element from this source wrapped in `Some` or `None` when the source is empty or fails during the receive operation.
519
+ * Note that `headOption` is not an idempotent operation on source as it receives elements from it.
520
+ *
521
+ * @return
522
+ * A `Some(first element)` if source is not empty or None` otherwise.
523
+ * @example
524
+ * {{{
525
+ * import ox.*
526
+ * import ox.channels.Source
527
+ *
528
+ * scoped {
529
+ * Source.empty[Int].headOption() // None
530
+ * val s = Source.fromValues(1, 2)
531
+ * s.headOption() // Some(1)
532
+ * s.headOption() // Some(2)
533
+ * }
534
+ * }}}
535
+ */
536
+ def headOption (): Option [T ] = Try (head()).toOption
537
+
538
+ /** Returns the first element from this source or throws `NoSuchElementException` when the source is empty or `receive()` operation fails
539
+ * without error. In case when the `receive()` operation fails with exception that exception is re-thrown. Note that `headOption` is not
540
+ * an idempotent operation on source as it receives elements from it.
541
+ *
542
+ * @return
543
+ * A first element if source is not empty or throws otherwise.
544
+ * @throws NoSuchElementException
545
+ * When source is empty or `receive()` failed without error.
546
+ * @throws exception
547
+ * When `receive()` failed with exception then this exception is re-thrown.
548
+ * @example
549
+ * {{{
550
+ * import ox.*
551
+ * import ox.channels.Source
552
+ *
553
+ * scoped {
554
+ * Source.empty[Int].head() // throws NoSuchElementException("cannot obtain head from the empty source")
555
+ * val s = Source.fromValues(1, 2)
556
+ * s.head() // 1
557
+ * s.head() // 2
558
+ * }
559
+ * }}}
560
+ */
561
+ def head (): T =
562
+ supervised {
563
+ receive() match
564
+ case ChannelClosed .Done => throw new NoSuchElementException (" cannot obtain head from an empty source" )
565
+ case ChannelClosed .Error (r) => throw r.getOrElse(new NoSuchElementException (" getting head failed" ))
566
+ case t : T @ unchecked => t
567
+ }
516
568
}
517
569
518
570
trait SourceCompanionOps :
519
571
def fromIterable [T ](it : Iterable [T ])(using Ox , StageCapacity ): Source [T ] = fromIterator(it.iterator)
520
572
521
- def fromValues [T ](ts : T * )(using Ox , StageCapacity ): Source [T ] = fromIterator(ts.iterator)
522
-
523
573
def fromIterator [T ](it : => Iterator [T ])(using Ox , StageCapacity ): Source [T ] =
524
574
val c = StageCapacity .newChannel[T ]
525
575
forkDaemon {
@@ -531,6 +581,8 @@ trait SourceCompanionOps:
531
581
}
532
582
c
533
583
584
+ def fromValues [T ](ts : T * )(using Ox , StageCapacity ): Source [T ] = fromIterator(ts.iterator)
585
+
534
586
def fromFork [T ](f : Fork [T ])(using Ox , StageCapacity ): Source [T ] =
535
587
val c = StageCapacity .newChannel[T ]
536
588
forkDaemon {
@@ -643,11 +695,6 @@ trait SourceCompanionOps:
643
695
}
644
696
c
645
697
646
- def empty [T ]: Source [T ] =
647
- val c = DirectChannel ()
648
- c.done()
649
- c
650
-
651
698
/** Sends a given number of elements (determined byc `segmentSize`) from each source in `sources` to the returned channel and repeats. The
652
699
* order of elements in all sources is preserved.
653
700
*
@@ -729,6 +776,11 @@ trait SourceCompanionOps:
729
776
}
730
777
c
731
778
779
+ def empty [T ]: Source [T ] =
780
+ val c = DirectChannel ()
781
+ c.done()
782
+ c
783
+
732
784
/** Creates a source that fails immediately with the given [[java.lang.Throwable ]]
733
785
*
734
786
* @param t
@@ -740,3 +792,13 @@ trait SourceCompanionOps:
740
792
val c = DirectChannel [T ]()
741
793
c.error(t)
742
794
c
795
+
796
+ /** Creates a source that fails immediately
797
+ *
798
+ * @return
799
+ * A source that would fail immediately
800
+ */
801
+ def failed [T ](): Source [T ] =
802
+ val c = DirectChannel [T ]()
803
+ c.error(None )
804
+ c
0 commit comments