We have seen how the spawn expression allow us to evaluate an expression in another thread:

spawn (1 + 2) @ Static

This allows us to write both concurrent and parallel programs. The downside is that we must manually coordinate communication between threads using channels. If want parallelism, but not concurrency, a more light-weight approach is to use the par expression.

The par expression:

par (1 + 2, 3 + 4)

evaluates 1 + 2 and 3 + 4 in parallel and returns a tuple with the result.

If we have expressions e1, e2, and e3 and we want evaluate them in parallel, we can write:

let (x, y, z) = par (e1, e2, e3)

which will spawn a thread for each of e1, e2, and e3 and bind the result to the local variables x, y, and z.

For convenience, Flix also offers the par-yield construct:

par (x <- e1; y <- e2; z <- e3) 
    yield x + y + z

which evaluates e1, e2, and e3 in parallel, binds their results to x, y, and z, and returns their sum.

We can use par-yield to write a parallel function:

def parMap(f: a -> b, l: List[a]): List[b] = match l {
    case Nil     => Nil
    case x :: xs => 
        par (r <- f(x); rs <- parMap(f, xs))
            yield r :: rs

This function will evaluate f(x) and parMap(f, xs) in parallel. Thus each recursive call spawns a new thread.