Applicative Effects
Parallellism
Two independent computations can be combined, giving potential for their parallel execution:
val foobar = foo *! bar
The *!
operator is an alias for zipPar
method (see Computation API).
The possibility of parallelism, depends on implementation of handlers. Parallelism is possible only when all handlers in the currently used effect stack, are implemented to permit parallelism.
-
If parallelization is possible, 2 fibers1 for
foo
andbar
are implicitly forked. Upon joining, results of each contributing effect are composed, in a similar manner as in composed Applicative Functor2. -
If parallelization is not possible,
zipPar
fallbacks to sequentialzip
.
This condition doesn’t apply to fibers forked & joined explicitly (WIP).
Parallelizability of predefined handlers
Predefined effect | Predefined handler for this effect | Is the handler parallelizable? |
---|---|---|
Reader |
default | ✅ |
Writer |
local |
✅ |
shared |
✅ | |
State |
local |
❌ |
shared |
✅ | |
Error |
first |
❌ |
all |
✅ | |
Choice |
first |
✅ |
all |
✅ | |
Random |
local |
✅ |
shared |
✅ | |
Console |
default | ✅ |
State
’s local
handler is conceptually similar to standard State
monad.
State updates are chained linearly.
It is inherently impossible to fork-and-join such chain
without having to make arbitrary choice about information loss.
For this reason, this handler prohibits parallellism.
See Everything you didn’t want to know about StateT ⚠️λ3 video for more information.
Error
’s first
handler short-circuits the computation on the first raised error.
This handler prohibits parallellism,
because the second computation can’t be run,
until it’s determined that the first one completes without error.
Example: Applicative vs. Monadic error
Depending on selected handler, given program will either:
-
Attempt to execute both branches sequentially, but will stop on the first error.
-
Attempt to execute both branches parallelly, and will collect both errors.
import turbolift.!!
import turbolift.effects.ErrorK
case object MyError extends ErrorK[List, String]
val program = MyError.raise("foo") &! MyError.raise("bar")
// program: Computation[Nothing, MyError] = turbolift.Computation@3b247ad
val result1 = program.handleWith(MyError.handlers.first).run
// result1: Either[List[String], Nothing] = Left(value = List("foo"))
val result2 = program.handleWith(MyError.handlers.all).run
// result2: Either[List[String], Nothing] = Left(value = List("foo", "bar"))