fold evaluates a pull incrementally until it terminates, applying an accumulator function to each emitted value. It returns both the final result and the accumulated state. section 15.2
Accumulating a pull
A pull can be folded with an initial accumulator and a function such as (total, value) => total + value. Each output becomes the next accumulator, while the pull's terminating result is preserved separately. section 15.2
Use fold when stream consumption should produce a controlled result rather than retain every output.
- Collect outputs with toList when retaining them is appropriate. section 15.3.1
- Discard outputs while executing effects by folding with unit: fold(())((_, _) => ()). section 15.3.1
- Manage scopes by stepping pulls, tracking the active scope, and closing the root scope after completion or failure. section 15.3.3
Listing 15.2 The Pull data type
enum Pull[+O, +R]:
case Result[+R](result: R) extends Pull[Nothing, R]
case Output[+O](value: O) extends Pull[O, Unit]
case FlatMap[X, +O, +R](
source: Pull[O, X],
f: X => Pull[O, R]) extends Pull[O, R]
def step: Either[R, (O, Pull[O, R])] = this match #1
case Result(r) => Left(r)
case Output(o) => Right(o, Pull.done)
case FlatMap(source, f) =>
source match
case FlatMap(s2, g) =>
s2.flatMap(x => g(x).flatMap(y => f(y))).step
case other => other.step match
case Left(r) => f(r).step
case Right((hd, tl)) => Right((hd, tl.flatMap(f)))
@annotation.tailrec
final def fold[A](init: A)(f: (A, O) => A): (R, A) = #2
step match
case Left(r) => (r, init)
case Right((hd, tl)) => tl.fold(f(init, hd))(f)
def toList: List[O] = #3
fold(List.newBuilder[O])((bldr, o) => bldr += o)(1).result
def flatMap[O2 >: O, R2](
f: R => Pull[O2, R2]
): Pull[O2, R2] = #4
Pull.FlatMap(this, f)
def >>[O2 >: O, R2](next: => Pull[O2, R2]): Pull[O2, R2] =
flatMap(_ => next)
def map[R2](f: R => R2]): Pull[O, R2] =
flatMap(r => Result(f(r)))
Inspect the Pull representation and its tail-recursive fold implementation. section 15.2
