1. 57
  1. 49

    Time for some explanations. Effect handlers are an exciting idea of the 2010s to support user-defined side effects in a programming language using a structured form of delimited continuations / resumable exceptions. This is an alternative to the dominant approach to user-defined side effects, which is to use monads or applicative functors. In short: it’s a cool language feature that lets people implement “effects” as libraries (generators, coroutines, controlled forms of global state, logging, etc.).

    Compared to “the standard Haskell way” of providing user-defined side effects:

    • Effect handlers may or may not come with a corresponding static type discipline: maybe the language tracks statically which effects a computation may perform, and maybe it doesn’t. Monads always change the types of the computation. This is good in theory and in simple cases, but it also raises a lot of ergonomics issues when combining several effects. If you want to type effect handlers, you naturally design an “effect system” (instead of hiding this information in the types of values), which can be more ergonomic than the Haskell approach thanks to specialized type inference etc. But in practice few languages do it well, it is still a research question. (Look at the experimental language Koka for ergonomic effect typing, and many other cool things.)
    • There is a lot of dicussions about whether effect handlers “compose better”, are “more flexible” than monads (and many proposals for “free monads”, “freeer monads” etc.). Experts discuss this and I’m not one of them, but my impression is that in complex cases (when effects are mixed with scoping, think try..catch for exceptions) all existing designs have downsides where reasonable people don’t like the behavior we get. Side-effects are tricky!
    • To summarize: paired with a reasonable effect system, effect handlers offer the promise of making “user-defined effects” easier to use for users, at least in the not-too-advanced cases. Then there are many open questions in the advanced cases.

    Effect handlers in OCaml 5:

    • It’s exciting because it’s (about to be) the first “real-world programming language” (not “mainstream”, but used in production, with a sizeable community) to include effect handlers.
    • The implementation in OCaml 5 is, by design, limited. It was built to allow user-defined schedulers for cooperative concurrency, rather than to express arbitrary effects. (The main limitation is “no multishot continuations”, see below.)
    • There is no effect system / type system yet. It’s easy to shoot yourself in the foot if you use it too much. People would like to have an effect system, but there is no timeline on when this could happen, possibly years.
    • Because there is no effect system yet, the decision was made to not provide a syntax for the feature, you have to use builtin libraries with higher-order functions. It feels like an encoding rather than proper language support, because it is: there is runtime support, but users are not encouraged to use it beyond “generators and cooperative concurrency”.
    • The support for effect handlers does not include support for multi-shot continuations, that is, re-invoking the same continuation/coroutine several times. This is fine for cooperative concurrency, generators, logging… but it imposes some limitations for other notions of effects, typically, things with backtracking. Some of the advanced stuff that can in theory be done with effect handlers will not be directly translatable to safe OCaml 5 code.
    1. 3

      Great summary.

      I first encountered effect handlers in Pyro, a library (kind of a DSL) for probabilistic programming built on top of PyTorch.

      For Pyro, these have been a success as they allow to customize inference without exposing the user to many caveats other systems have.