So this looks like dynamically scoped callbacks. Instead of passing callbacks along as parameters they are declared as “handlers”, and any function down the call stack can invoke them. Is this a correct understanding?
That's what I was thinking. You could get almost all of this pretty directly in Javascript by putting a callback function in an AsyncLocalStorage instance or, in other languages, in a thread local variable.
I was thinking this through more and realized that a callback directly in a thread local variable isn't quite enough: You need to keep a stack in the thread local variable. Every try-handle block pushes a callback in and pops it when exited. (Javascript's AsyncLocalStorage is naturally nestable so you don't need to manage a stack yourself with it.)
That's very interesting, thanks! It gave me a brainwave and I wondered I could implement that in Bluefin. I'm pretty sure Bluefin's Request[1] is a second class stackful coroutine, and sure enough it turns out to be possible, so I'm pleased about that.
-- ghci> example
-- Hello
-- World
-- Timed out
example = runEff $ \io -> awaitYield (receiver io) sender
receiver ::
(e1 <: es, e2 <: es) =>
IOE e1 ->
Await String e2 ->
Eff es ()
receiver io a = do
r1 <- await a
effIO io (putStrLn r1)
r2 <- await a
effIO io (putStrLn r2)
mr3 <- timeout io 0 (await a)
effIO io $ case mr3 of
Nothing -> putStrLn "Timed out"
Just r3 -> putStrLn r3
sender ::
e1 <: es =>
Yield String e1 ->
Eff es ()
sender y = do
yield y "Hello"
yield y "World"
yield y "More"
timeout ::
e1 <: es =>
IOE e1 ->
Int ->
Eff es r ->
Eff es (Maybe r)
timeout io t m = withEffToIO
(\effToIO -> System.Timeout.timeout t (effToIO (\_ -> useImpl m)))
io
Or in Lua you'd wrap the initial call in a coroutine, possibly with coronest[a] or something similar to make handling the effects at the right layer easier.
And so then the outer code is a loop around coroutine.resume, and the inner code uses coroutine.yield to perform an effect.
An effect system is an extension of a type system where a function type encodes inputs, outputs and effects.
The article mentions that they handwaved all the typing stuff (when you do that, effect handlers are more like delimited continuations). But the types are important. If a function doesn't tell you it's effects, how do you know you have to handle them? You'd have to read the whole call graph. That's why exceptions suck.