Algebraic Effects

Just throwing this out there: in the meantime, I've come up with a "system" to do something like the "monad interpreter pattern" that kinda does the algebraic effect trick, except in a more traditional monad-y way instead of keywords.

I encourage you to try and play with the Free template.

The cool thing about this approach is that adding "new effects" is as simple as adding an enum case. This will automatically force you to rewrite all your "effect handlers" so they handle the new effect. Adding new monadic "target types" into which you can parse your monad is just a little boilerplate, but the template already comes with a few that you can use/change.

This gives us the necessary hint how we ought to think of "combining" effects: since we're working on a restricted subset of all monads (namely the free monads over some "alphabet"), we can think of effects as a kind of sum type! Combining them is super easy, barely an inconvenience.

Thinking about an algebraic effect system, I'd by now imagine it as follows:

  1. One declares a data type that can represent an effect and annotates the type with @Effect. The annotation will require one to define keywords similar to "throws", "rethrows" and "try", while throwing will always happen via the keyword "throw".
  2. Stacking effects can be done in any arbitrary order in the declaration of the function. All that this does is that in the background, a new sum type is generated that can store each effect; and a new "free" type is generated that allows for chaining effects.
  3. Effect handlers (do/catch) simply parse the effects into other effects that are declared in the enclosing function declaration. No effect in the declaration of the enclosing function means that you actually run the code.

That's at least a mental model. I'm sure this can be built deeper into the compiler so that generating a sum type and a free type isn't necessary.

3 Likes