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:
    • Frank language.
    • Unison language, which implements Frank’s effect system.
  • 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:

HOEs in Turbolift

In this example, we run the given program twice, with 2 orderings of handlers:

  1. Error handled before State.
  2. State handled before Error.

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.


  1. Warning: Haskell code ahead.  2 3