The main types of Turbolift, and their roles

  • Computation - Monad, parametrized by set of effects, a.k.a “One Monad To Rule Them All” 1.
  • Signature - Trait, where we define our Algebra/Service/DSL (as abstract methods).
  • Effect - Object, through which we can invoke operations of some Algebra/Service/DSL (as concrete methods).
  • Interpreter - Object that assigns semantics to some Algebra/Service/DSL. It produces a Handler.
  • Handler - Object that we can use like generalized try ... catch expression: to delimit scope of effect(s).

Computation Usage

A value of type Computation[+A, -U] describes a… computation, that requests a set of effects U, that need to be handled, before it can return a value of type A.

A type alias !![A, U] is defined for convenient infix syntax.

The type-level set of effects is modelled by intersection types:

Scala type Meaning as a set of effects Sample computation type Same, using infix syntax
Any Computation[Int, Any] Int !! Any
X X Computation[Int, X] Int !! X
X & Y XY Computation[Int, X & Y] Int !! (X & Y)


Usually, Scala compiler can infer the set of effects requested by the computation. We can see this in code example on the front page. The inferred type of program indicates, that it requests 3 effects: MyReader, MyState and MyError.


Additionally, !! is a value alias of Computation’s companion object:

import turbolift.!!

val myComputation1 = !!.unit
// myComputation1: Computation[Unit, Any] = turbolift.Computation@121b83a5

val myComputation2 = !!.pure(42)
// myComputation2: Computation[Int, Any] = turbolift.Computation@7c05089b

For more information, see Computation API.

Effect Usage

To be able to invoke the effect’s operations, we need access to an instance of the effect.

We can create such instance ourselves:

// State inherits from Effect:
import turbolift.effects.State

// Instantiation:
case object MyState extends State[Int]

// Invoking operation:
val computation = MyState.put(42)
// computation: Computation[Unit, MyState] = turbolift.Computation@62f0977d

For more details, see Defining your own effects & handlers and Effect labelling.

Handler Usage

Application of a handler delimits scope of effect(s). It also transforms type of the computation. In the simplest case, one of effects requested by the computation is eliminated.

  val myComputation2 = myComputation1.handleWith(myHandler)

As soon as all effects are eliminated, the computation’s result can be obtained, using run:

  val result = myComputation

…or using unsafeRun, if the only effect remaining unhandled, is IO.

In general, a handler of type Handler[F[_], G[_], L, N] represents a polymorphic function, that transforms computations:

 A, M.  Computation[F[A], M  L] => Computation[G[A], M  N]

// Where `F[_]` is either type-level identity, or constant function.

Meaning, that application of it, does the following:

  • It eLiminates set of effects L from incoming computation.
  • It iNtroduces set of effects N into outgoing computation (revealing dependencies of the handler, if there are any).
  • It passes aMbient set of effects M unaffected, from incoming to outgoing computation.
  • It applies type constructor G[_] to A.
  • It constraints type returned by the incoming computation to be equal F[A].

In the example below, myHandler eliminates single MyChoice effect, introduces no effects, accepts any type of computation (identity), and wraps the result type in Vector[_].

import turbolift.Handler
import turbolift.effects.Choice

case object MyChoice extends Choice
type MyChoice = MyChoice.type

val myHandler: Handler[[X] =>> X, Vector, MyChoice, Any] = MyChoice.handler

Handlers can be transformed or composed in various ways.

For example, this sequences 3 independent handlers:

val myHandler123 = myHandler1 &&&! myHandler2 &&&! myHandler3

For more operations, see Handler API.

Signature & Interpreter Usage

Those 2 types are used only during Defining your own effects & handlers.

  1. Slogan coined by Eric Torreborre