Labelled Effects
Ability to label effects, is a very rarely found feature in effect systems. That’s a shame, because without it, there is no true modularity.
Related reading:
- Idris language - Labelled Effects
- Helium language - Effect Instances
In Idris and Helium, effect labelling is optional.
In Turbolift, effects are always uniquely labeled, thanks to Scala’s singleton types:
import turbolift.effects.Error
// Unique value:
case object MyError extends Error[String]
// Unique type:
type MyError = MyError.type
There is nothing stopping us from instantiating given effect more than once:
import turbolift.effects.Error
case object MyError1 extends Error[String]
case object MyError2 extends Error[String]
type MyError1 = MyError1.type
type MyError2 = MyError2.type
Each instance is a fully independent effect:
- They may be instantiated with different type parameters (e.g.
State[Int]
andState[String]
). - They may be used together in a computation.
The type of the computation will reflect this, showing 2 distinct effects (e.g.
MyError1 & MyError2
) - They may be handled at different points in program, and with different handlers.
Example: Using 2 State effects at the same time
import turbolift.!!
import turbolift.effects.State
case object Foo extends State[Int]
case object Bar extends State[Int]
val program =
for
x <- Foo.get
_ <- Bar.put(-x)
yield ()
// program: Computation[Unit, Foo & Bar] = turbolift.Computation@4f084cc4
val result = program
.handleWith(Foo.handler(42))
.handleWith(Bar.handler(1337))
.run
// result: Tuple2[Tuple2[Unit, Int], Int] = (((), 42), -42)
Performance
Performance penalty for using multiple effects in Turbolift, is small. Here is comparison with old version of Turbolift, that was based on Monad Transformers: