@@ -5,6 +5,7 @@ import ox.*
5
5
import java .util .concurrent .{CountDownLatch , Semaphore }
6
6
import scala .collection .mutable
7
7
import scala .concurrent .duration .FiniteDuration
8
+ import scala .collection .IterableOnce
8
9
9
10
trait SourceOps [+ T ] { this : Source [T ] =>
10
11
// view ops (lazy)
@@ -311,6 +312,113 @@ trait SourceOps[+T] { this: Source[T] =>
311
312
def drain (): Unit = foreach(_ => ())
312
313
313
314
def applied [U ](f : Source [T ] => U ): U = f(this )
315
+
316
+ /** Applies the given mapping function `f`, using additional state, to each element received from this source, and sends the results to
317
+ * the returned channel. Optionally sends an additional element, possibly based on the final state, to the returned channel once this
318
+ * source is done.
319
+ *
320
+ * The `initializeState` function is called once when `statefulMap` is called.
321
+ *
322
+ * The `onComplete` function is called once when this source is done. If it returns a non-empty value, the value will be sent to the
323
+ * returned channel, while an empty value will be ignored.
324
+ *
325
+ * @param initializeState
326
+ * A function that initializes the state.
327
+ * @param f
328
+ * A function that transforms the element from this source and the state into a pair of the next state and the result which is sent
329
+ * sent to the returned channel.
330
+ * @param onComplete
331
+ * A function that transforms the final state into an optional element sent to the returned channel. By default the final state is
332
+ * ignored.
333
+ * @return
334
+ * A source to which the results of applying `f` to the elements from this source would be sent.
335
+ * @example
336
+ * {{{
337
+ * scala>
338
+ * import ox.*
339
+ * import ox.channels.Source
340
+ *
341
+ * scoped {
342
+ * val s = Source.fromValues(1, 2, 3, 4, 5)
343
+ * s.mapStateful(() => 0)((sum, element) => (sum + element, sum), Some.apply)
344
+ * }
345
+ *
346
+ * scala> val res0: List[Int] = List(0, 1, 3, 6, 10, 15)
347
+ * }}}
348
+ */
349
+ def mapStateful [S , U >: T ](
350
+ initializeState : () => S
351
+ )(f : (S , T ) => (S , U ), onComplete : S => Option [U ] = (_ : S ) => None )(using Ox , StageCapacity ): Source [U ] =
352
+ def resultToSome (s : S , t : T ) =
353
+ val (newState, result) = f(s, t)
354
+ (newState, Some (result))
355
+
356
+ mapStatefulConcat(initializeState)(resultToSome, onComplete)
357
+
358
+ /** Applies the given mapping function `f`, using additional state, to each element received from this source, and sends the results one
359
+ * by one to the returned channel. Optionally sends an additional element, possibly based on the final state, to the returned channel
360
+ * once this source is done.
361
+ *
362
+ * The `initializeState` function is called once when `statefulMap` is called.
363
+ *
364
+ * The `onComplete` function is called once when this source is done. If it returns a non-empty value, the value will be sent to the
365
+ * returned channel, while an empty value will be ignored.
366
+ *
367
+ * @param initializeState
368
+ * A function that initializes the state.
369
+ * @param f
370
+ * A function that transforms the element from this source and the state into a pair of the next state and a
371
+ * [[scala.collection.IterableOnce ]] of results which are sent one by one to the returned channel. If the result of `f` is empty,
372
+ * nothing is sent to the returned channel.
373
+ * @param onComplete
374
+ * A function that transforms the final state into an optional element sent to the returned channel. By default the final state is
375
+ * ignored.
376
+ * @return
377
+ * A source to which the results of applying `f` to the elements from this source would be sent.
378
+ * @example
379
+ * {{{
380
+ * scala>
381
+ * import ox.*
382
+ * import ox.channels.Source
383
+ *
384
+ * scoped {
385
+ * val s = Source.fromValues(1, 2, 2, 3, 2, 4, 3, 1, 5)
386
+ * // deduplicate the values
387
+ * s.mapStatefulConcat(() => Set.empty[Int])((s, e) => (s + e, Option.unless(s.contains(e))(e)))
388
+ * }
389
+ *
390
+ * scala> val res0: List[Int] = List(1, 2, 3, 4, 5)
391
+ * }}}
392
+ */
393
+ def mapStatefulConcat [S , U >: T ](
394
+ initializeState : () => S
395
+ )(f : (S , T ) => (S , IterableOnce [U ]), onComplete : S => Option [U ] = (_ : S ) => None )(using Ox , StageCapacity ): Source [U ] =
396
+ val c = StageCapacity .newChannel[U ]
397
+ forkDaemon {
398
+ var state = initializeState()
399
+ repeatWhile {
400
+ receive() match
401
+ case ChannelClosed .Done =>
402
+ try
403
+ onComplete(state).foreach(c.send)
404
+ c.done()
405
+ catch case t : Throwable => c.error(t)
406
+ false
407
+ case ChannelClosed .Error (r) =>
408
+ c.error(r)
409
+ false
410
+ case t : T @ unchecked =>
411
+ try
412
+ val (nextState, result) = f(state, t)
413
+ state = nextState
414
+ result.iterator.map(c.send).forall(_.isValue)
415
+ catch
416
+ case t : Throwable =>
417
+ c.error(t)
418
+ false
419
+ }
420
+ }
421
+ c
314
422
}
315
423
316
424
trait SourceCompanionOps :
0 commit comments