Higher Order Effects
a.k.a Scoped Effects.
HOEs are problematic
- New programming languages with native Algebraic Effects, generally don’t support HOEs. Exceptions are:
-
According to the underlying theory, HOEs are actually non-algebraic ⚠️λ1.
-
The Eff Monad doesn’t support HOEs.
- Monad Transformers do support HOEs. However, there are some known problems. Such as effect’s semantics being dependent
on the order of monad transformers in the stack. More info on the subject:
- Unresolved challenges of scoped effects ⚠️λ1 video.
- Effect Semantics Zoo ⚠️λ1
HOEs in Turbolift
In this example, we run the given program twice, with 2 orderings of handlers:
Error
handled beforeState
.State
handled beforeError
.
We observe consistent behavior: in both cases,
raising the error didn’t cause the State
to reset to it’s value from
before the catchAll
operation.
import turbolift.!!
import turbolift.effects.{Error, State}
case object MyError extends Error[String]
case object MyState extends State[Int]
val program =
MyError.catchAll {
MyState.put(42) &&!
MyError.raise("error")
} {
case _ => !!.pure("nvm")
}
// program: Computation[Computation[String, Any], MyState & MyError] = turbolift.Computation@564d23b7
val result1 = program
.handleWith(MyError.handler)
.handleWith(MyState.handler(0))
.run
// result1: Tuple2[Either[String, Computation[String, Any]], Int] = (
// Right(value = turbolift.Computation@f05a623),
// 42
// )
val result2 = program
.handleWith(MyState.handler(0))
.handleWith(MyError.handler)
.run
// result2: Either[String, Tuple2[Computation[String, Any], Int]] = Right(
// value = (turbolift.Computation@2e0ffc4, 42)
// )
🎁 Bonus feature
The implementation of HOEs in Turbolift has an accidental consequence.
It’s possible to write an alternative handler for the Error
effect,
so that it replicates behavior of Monad Transformers.
With such handler, state is transactional, when handled before error.