liveBook · Manning

Fork

As explained in Functional Programming in Scala, Second Edition

fork is a book-defined parallel-computation operation that evaluates a computation asynchronously in a separate logical thread while preserving its result.

`fork` marks a `Par[A]` computation for concurrent evaluation in a separate logical thread. Its argument is nonstrict, so the work is deferred until `run` forces the parallel computation. Unlike `map2`, which combines two results, `fork` answers a separate question: which work should be asynchronous? Keeping those decisions apart puts parallelism under explicit programmer control rather than imposing one global policy. section 7.1.3 section 7.2

Forking only the recursive work

Parallel summation can leave the tiny base case immediate and fork each recursive half. `map2` then combines the two completed sums. The placement of `fork` is visible in the program, making the concurrency decision inspectable rather than an automatic side effect of combination. section 7.1.3

def sum(ints: IndexedSeq[Int]): Par[Int] =
  if ints.size <= 1 then
    Par.unit(ints.headOption.getOrElse(0))
  else
    val (l, r) = ints.splitAt(ints.size / 2)
    Par.map2(Par.fork(sum(l)), Par.fork(sum(r)))(_ + _)

Inspect a divide-and-conquer sum where each nontrivial branch is explicitly wrapped in `Par.fork`. section 7.1.3

A strict `unit` can promote an already available value to `Par`, while `lazyUnit(a)` is derived as `fork(unit(a))`. This distinction is useful: strictness controls when an expression is evaluated, whereas `fork` marks the resulting parallel computation for asynchronous execution. Small composable primitives can therefore express a nonstrict convenience operation without making it primitive. section 7.1.3 section 7.2

A desirable law is `fork(x) == x`: forking may change scheduling, but not the result of a parallel computation. The law is more than a slogan. With a bounded thread pool, a naïve implementation that submits work and then calls `Future.get` inside the submitted task can deadlock. Enough nested forks can occupy every worker with blocked waits while the remaining work has no thread on which to run. section 7.3.2 section 7.3.3 section 7.9

A non-blocking representation avoids calling `Future.get` while implementing `fork` or `map2`. Instead of exposing a future from which a value is pulled, it represents completion through a callback. `fork` schedules invocation of the supplied computation and its callback; `map2` coordinates both callbacks before applying its combining function. The outer `run` may still wait for the final answer, but internal composition avoids tying up worker threads. section 7.3.4

def fork[A](a: => Par[A]): Par[A] =
  es => cb => eval(es)(a(es)(cb))                   #1
 
def eval(es: ExecutorService)(r: => Unit): Unit =   #2
  es.submit(new Callable[Unit] { def call = r })

Compare the non-blocking `fork` implementation: it submits callback delivery without waiting on `Future.get`. section 7.3.4

Explicit forking also supports higher-level combinators. `asyncF` turns an ordinary function into one that returns a forked `Par`; `parMap` uses it for element computations and wraps its setup in `fork` so constructing a large parallel mapping returns promptly. The result-collection problem remains separate and is handled by `sequence`. section 7.2.1 section 7.9

How Functional Programming in Scala, Second Edition covers it

How deep it goes
Defines the API and law, then examines blocking deadlocks and a callback-based non-blocking implementation.
Examples
Uses divide-and-conquer summation, fixed-size thread pools, nested forks, `asyncF`, and `parMap` to expose scheduling behavior.
What it assumes
Builds from `Par` combinators and introduces the needed distinction between strictness, deferred evaluation, and asynchronous execution.
What you can do after
Diagnose/debug with it by testing `fork(x) == x` against bounded thread pools and avoiding blocking worker waits.
How it connects
Connects `fork` with `Par`, `map2`, `unit`, `lazyUnit`, `run`, `Future`, `parMap`, and `sequence`.

Related concepts

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.