liveBook · Manning

Parallel map2

As explained in Functional Programming in Scala, Second Edition

Parallel map2 is a book-defined higher-order function that combines the results of two parallel computations with a binary function.

Parallel map2 combines two independent Par computations with a binary function, producing a new Par rather than extracting either result. Its general shape is pa.map2(pb)(f): Par[C], where pa yields A, pb yields B, and f combines A and B into C. Keeping the result inside Par avoids calling run during composition. section 7.1.2 section 7.2

The key design question is evaluation order. A strict map2 follows Scala’s left-to-right argument evaluation, expanding the entire left branch of a recursive sum before considering the right. That conflicts with the intended meaning: the two inputs represent work that can be evaluated in parallel, with neither side privileged by argument order. section 7.1.2

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(sum(l), sum(r))(_ + _)

A divide-and-combine sum makes the role of map2 concrete: each recursive half returns Par[Int], and map2 adds their eventual results without running them directly. section 7.1.2

map2 is also the primitive from which map can be derived. Pair a computation with unit(()) and ignore the second value; then a function A => B transforms Par[A] into Par[B]. This lets a sorting request remain a Par[List[Int]] rather than running, sorting, and repackaging the list. section 7.2.1

The same combination pattern supports several useful operations:

  • Define equality of two parallel computations as a Par[Boolean] with p.map2(p2)(_ == _), then run only the combined comparison. section 8.2.3
  • Support parMap by asynchronously lifting each element computation and sequencing the resulting List[Par[B]]. section 7.2.1
  • Combine an ExecutorService generator and an input generator when property-testing parallel computations across execution strategies. section 8.2.3

Comparing parallel results

Instead of running both sides separately, equal(Par.unit(1).map(_ + 1), Par.unit(2)) builds one Par[Boolean] through map2. A property can then run that single value under generated executor services. section 8.2.3

How Functional Programming in Scala, Second Edition covers it

How deep it goes
Develops the generic signature, strictness consequences, basic implementation, and timeout-correctness exercise.
Examples
Uses a divide-and-conquer integer sum, parallel list sorting, equality testing, and executor-generator pairing.
What you can do after
Apply it to combine parallel results, derive map, and express equality checks without separately running both computations.
How it connects
Connects Par with unit, run, fork, lazyUnit, map, parMap, sequence, Future, and property-based testing.

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.