In the Monte programming language, as well as in its ancestor E, there is syntax for single-shot delimited continuations, called "ejectors," which are continuations that are effectively only usable once within a syntactic boundary. For example, here is an ejector that is not called:
escape ej { 42 }
And an ejector that is called:
escape ej { ej(42); "After 10,000 years, I'm free!" }
Both evaluate to 42
. We can also attach an action to the case where the ejector is called:
escape ej { ej(2) } catch result { result + 4 }
That's about all of this syntax. It's not hard to imagine how this mimics Maybe
or Either
. I'll transcribe the example from the Haskell Wiki on Maybe into idiomatic Monte:
def f(x, ej):
return if (x == 0) { ej() } else { x }
def g(x, ej):
return if (x == 100) { ej() } else { x }
def h(x, ej):
return g(f(x, ej), ej)
# Monte permits Unicode identifiers but not for free.
def ::"h´"(x, ej):
def n := f(x, ej)
return g(n, ej)
(Note how we must bear the burden of passing ej
. Monte lacks the "programmable semicolon" of do-notation.)
I won't do Either
, but it's largely the same; the ability to add a catch
clause provides the required type discrimination. Delimited continuations are well-known to compose, so it's possible to build complex tools:
def trying(f, g, ej):
return escape innerEj { f(innerEj) } catch _ { g(ej) }
These kinds of gadgets are used in Monte to build hand-written parser combinators, for example. So, in The Mother of all Monads, Dan Piponi explains that Cont
is, in a certain sense, a very primal Monad
upon which many other Monad
s can be built. We can attempt to do this in Monte, too. Let's use Moggi's style for encoding monads in object-based languages:
object identity:
to unit(x):
return x
# "bind" is a Monte keyword; we may still use it, but not for free.
# NB this is `x >>= f` in Haskell.
to "bind"(x, f):
return f(x)
And let's encode the binding helper i
, to get an idea of what it looks like:
def i(m, x, ej):
return m."bind"(x, ej)
...This isn't helpful. This isn't looking like good syntax.
# Haskell: runIdentity $ do { x <- return 32; return $ x // 4 }
escape ej { i(identity, i(identity, identity.unit(32), fn x { identity.unit(x // 4) }), ej) }
The future was supposed to have cool robots, not this. There's another problem, though; let's encode another traditional Monad
, List
:
object list:
to unit(x) { return [x] }
to "bind"(action, f):
var rv := []
for x in (action) { rv += f(x) }
return rv
And let's do a traditional Cartesian product. First, let's do a direct computation; instead of passing a continuation, we'll just bind directly using the list monad:
▲> i(list, [1, 2, 3], fn x { i(list, [4, 5, 6], fn y { [x * y] }) })
Result: [4, 5, 6, 8, 10, 12, 12, 15, 18]
And now with an ejector:
▲> escape ej { i(list, i(list, [1, 2, 3], fn x { i(list, [4, 5, 6], fn y { [x * y] }) }), ej) }
Result: 4
So! That's pretty interesting. The complete list monad computation is being run, but the ejector only sees the first item in the list. I suspect that, since ejectors are composable, there's a way to build a more sophisticated logic monad which does a better job of not computing so many intermediate results.
My question, then, is this: When we transform Maybe
and Either
into idiomatic Monte, we can clearly see that ejector syntax applies elegantly. What other monads are out there which would have interesting single-shot behavior like this? Don't feel limited to Haskell; Monte is untyped, so nobody will shun you for hard-to-type monads!