liveBook · Manning

Join

As explained in Functional Programming in Scala, Second Edition

join is a monadic combinator that flattens a nested monadic value into a single monadic layer.

`join` removes one layer of a monadic context: given `F[F[A]]`, it produces `F[A]`. It is the flattening operation behind a useful decomposition of `flatMap`: first `map` a function that returns `F[B]`, producing `F[F[B]]`; then `join` the result. section 11.7 section 7.4

def join[A](ppa: Par[Par[A]]): Par[A]

The type states the entire job: a nested parallel computation becomes one parallel computation. section 7.4

For `Par`, joining means running the outer computation, waiting for its result to reveal an inner computation, then running that inner computation. This lets later parallel work be selected by an earlier result rather than fixed in advance. section 7.9 section 12.3.1

`join` and the other monadic combinators can define one another:

  • Define `join` with `flatMap(identity)`: the contained value is already the next monadic computation. section 7.9 section 11.7
  • Define `flatMap(f)` as `map(f).join`, separating nesting from flattening. section 7.9 section 11.7
  • Use `unit`, `map`, and `join` as a minimal basis for a monad, alongside equivalent bases using `flatMap` or `compose`. section 11.4.3 section 11.7

Flattening a parallel computation

If mapping `f: A => Par[B]` over `pa: Par[A]` yields `Par[Par[B]]`, `join(pa.map(f))` yields `Par[B]`. In the direct implementation, the outer `Par` is run and awaited; its returned `Par` is then run on the same executor service. section 7.9

Flattening is capability that `Applicative` operations do not supply. `unit` can add a context layer, and `map2` can combine values within contexts, but neither removes an `F` layer. Consequently, dependent computations require monadic operations such as `join` or `flatMap`. section 12.3 section 12.3.1

The associativity law for `join` says that a three-layer value can be flattened from the outside or inside first with an equivalent result: `x.join.join == x.map(_.join).join`. For `Par`, this equates alternative orders of awaiting nested computations; for parsers, it preserves the order of dependent parsers regardless of nesting. section 11.7

How Functional Programming in Scala, Second Edition covers it

How deep it goes
Defines join by type and implementation, then derives flatMap and restates associativity through join.
Examples
Uses nested Par computations, Option lookups, and associativity interpretations for Par and Parser.
What you can do after
Build it from scratch with flatMap or implement flatMap from map and join.
How it connects
Connects join to Monad, flatMap, map, unit, compose, Applicative, Par, Option, and Parser.

Also explained in

Compare how 3 books explain join →

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.