`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
