Getting Started

Before you can get started with Effekt you need to set up your environment and add Effekt as a dependency to your build.sbt file:

resolvers += Opts.resolver.sonatypeSnapshots

libraryDependencies += "de.b-studios" %% "effekt" % "0.4-SNAPSHOT"

Alternatively, to play around with Effekt in your browser, you can also use Scastie. We prepared two different Scasties for you

  • Scala 2.12
  • Dotty – Here you can also play around with implicit function types. The interface is slightly different at the moment.

Define your first effect

First we import all the necessary types and functions. Then, to define our first effect, we specify the effect signature.

import effekt._

// The effect signature
trait Amb {
  def flip(): Control[Boolean]

Effect signatures in Effekt are ordinary Scala traits that simply declare the effect operations.

For programming with effects the most important type is Control. For now we only have to know that all effect operations like flip have to be marked as returning something in Control.

Using the Amb effect

To actually use the flip effect we need to get our hands on a capability (that is, an instance of the effect signature Amb) that entitles us to do so. Since we don’t yet know where to get such capabilities from, we just define a function asking for it. We flip a coin once and then return 2 or 3 depending on the result.

def prog(amb: Amb): Control[Int] = for {
  x <- amb.flip()
} yield if (x) 2 else 3

The result type Control[Int] tells us that the integer-result will be contained in the Control monad which is Effekt specific.

Defining Handlers

Let us now define our own handler for the Amb effect. A handler is just an implementation of the effect interface. However, it also needs to give the type to interpret the effect into (List[R]). Note how we make sure that the result of the handled program is a list by mapping r => List(r) over it before applying the handler. This way we convert the result type from R into the semantic domain List[R].

def ambList[R](prog: Amb => Control[R]): Control[List[R]] =
  new Handler[List[R]] with Amb {
    def flip() = use { resume => for {
      ts <- resume(true)
      fs <- resume(false)
    } yield ts ++ fs }
  } handle { amb => prog(amb) map { r => List(r) } }

Notice how we use the trait Handler which provides us with the method use. To implement flip we get access to the continuation resume (the remaining program after the flip effect, up to the handler).

The type of the expression passed to use is thus:

type Body = (Boolean => Control[List[R]]) => Control[List[R]
//          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//                  the continuation

Note how both, the continuation and the result type, coincide to be Control[List[R]].

Handling Effects

To handle an effect we use the apply method that is defined on an instance of handler.

val handled: Control[List[Int]] = ambList { amb => prog(amb) }

One way of thinking about algebraic effects and handlers is to think as (resumable) exceptions. In our example, the effect operation flip would correspond to throw and the handler ambList to a surrounding try { ... }.

After all effects are handled, we can now run the effectful computation:

run { handled }
// res0: List[Int] = List(2, 3)