Handling Multiple Effects at Once
Sometimes the granularity of handlers does not coincide with the granularity of effect signatures. For instance, we might want to offer fine grained interfaces for reading and writing operations but handle them together in one handler.
In this tutorial, we’ll see that in Effekt it is very natural to
define such combined handlers. Let’s start with defining the
effect signatures for Reader
and Writer
.
You can play around with the full source code for this example at this Scastie (Scala 2.12).
import effekt._
trait Reader[S] {
def read(): Control[S]
}
trait Writer[S] {
def write(s: S): Control[Unit]
}
(We omit the standard companion object definitions as seen in the earlier tutorials.)
Defining the combined handler
Since handlers of algebraic effects are just implementations of
the effect signature traits it is not surprising that one handler
might implement multiple of such signatures. Below, we define a
simple handler for Reader
and Writer
that uses a list to mediate
between the two effects.
def rwHandler[R, S] = new Handler[R] with Reader[S] with Writer[S] with State {
// create a new field
val out = Field(List.empty[S])
def read() = out.value flatMap {
case s :: rest => for {
_ <- out.value = rest
} yield s
case _ => sys error "Not enough elements written to perform read"
}
def write(s: S) = for {
rest <- out.value
_ <- out.value = s :: rest
} yield ()
}
Example usage
To show the usage of the combined handler, let’s first define a simple example program.
def example(implicit r: Reader[Int], w: Writer[Int]): Control[Int] =
for {
_ <- w.write(2)
_ <- w.write(3)
x <- r.read()
_ <- w.write(x * 2)
y <- r.read()
} yield y
Handling the example with our combined handler, we get:
run {
rwHandler handle { implicit rw: Reader[Int] with Writer[Int] =>
example
}
}
// res0: Int = 6