liveBook · Manning

Uncons

As explained in Functional Programming in Scala, Second Edition

Uncons is a Pull data constructor that stores a source pull whose next result is obtained when the constructor is evaluated.

uncons is a Pull data constructor that stores an underlying source pull instead of stepping it immediately. Its type records that observing the source produces no output and eventually yields either the source result or an output value with a remainder pull. section 15.3

The constructor resolves a tension between ordinary and effectful pulls:

  • Calling step immediately would require a Monad constraint while constructing uncons, making the operation unusable for non-effectful Pull values with F = Nothing. section 15.3
  • Storing the source defers step until the resulting pull is evaluated, preserving laziness and allowing operations such as take to consume one element at a time. section 15.2.1 section 15.3
  • When step encounters Uncons(source), it steps the source and wraps the result in a Left, leaving the constructor itself output-free. section 15.3

Using delayed observation in take

def uncons: Pull[Nothing, Either[R, (O, Pull[O, R])]] = Pull.done >> Result(step) def take(n: Int): Pull[O, Option[R]] = if n <= 0 then Result(None) else uncons.flatMap: case Left(r) => Result(Some(r)) case Right((hd, tl)) => Output(hd) >> tl.take(n - 1) Here, take observes the next source result through uncons rather than eagerly invoking step. The same pattern supports drop, takeWhile, dropWhile, mapOutput, and stateful transformations. section 15.2.1 section 15.6

val done: Pull[Nothing, Unit] = Result(())

Compare the delayed observation pattern with Pull.done, the output-free completed pull used to terminate recursive operations. section 15.2.1

How Functional Programming in Scala, Second Edition covers it

How deep it goes
Defines the constructor's type, storage, and step case, but leaves broader effectful execution to step, fold, and Monad.
Examples
Uses take, drop, takeWhile, dropWhile, mapOutput, filter, tally, and mapAccumulate as recursive consumers of uncons.
What it assumes
Assumes Pull and step, then requires a Monad for effectful stepping while avoiding a Monad[Nothing] requirement for ordinary pulls.
What you can do after
Build effectful Pull operations that defer source stepping and preserve compatibility with ordinary non-effectful pulls.
How it connects
Connects Pull to step, Result, Eval, FlatMap, and Monad through deferred observation of a source pull.

This explainer was generated by Manning's knowledge graph from the text of Functional Programming in Scala, Second Edition. The section links point to the parts of the book it draws on.