Running computations in parallel

A number of computations can be ran in parallel using the par method, for example:

import ox.{par, sleep}
import scala.concurrent.duration.*

def computation1: Int =
  sleep(2.seconds)
  1

def computation2: String =
  sleep(1.second)
  "2"

val result: (Int, String) = par(computation1, computation2)
// (1, "2")

If any of the computations fails, the other is interrupted. In such case, par waits until both branches complete and then re-throws the exception.

It’s also possible to run a sequence of computations given as a Seq[() => T] in parallel, optionally limiting the parallelism using parLimit:

import ox.{parLimit, sleep}
import scala.concurrent.duration.*

def computation(n: Int): Int =
  sleep(1.second)
  println(s"Running $n")
  n*2

val computations = (1 to 20).map(n => () => computation(n))
val result: Seq[Int] = parLimit(5)(computations)
// (1, "2")

Using application errors

Some values might be considered as application errors. If a computation returns such an error, other computations are interrupted, same as when an exception is thrown. The error is then returned by the par method.

It’s possible to use an arbitrary error mode by providing it as the initial argument to par. Alternatively, a built-in version using Either is available as parEither:

import ox.{parEither, sleep}
import scala.concurrent.duration.*

val result = parEither(
  {
    sleep(200.millis)
    Right("ok")
  }, {
    sleep(100.millis)
    Left(-1)
  }
)

// result is Left(-1), the other branch is interrupted