A Language Feature to Rule Them All - by Sir Whinesalot
Burning the Midnight Coffee
SubscribeSign in
A Language Feature to Rule Them All<br>The expressive power of Monads and Algebraic Effects.
Sir Whinesalot<br>Jul 30, 2026
Share
Some language features are too powerful for their own good.<br>Do you know what a Monad is1? People have struggled to explain what they are, which is not particularly surprising since it is a very abstract concept. There’s even a timeline recording the history of (in)famous monad tutorials. Many try to explain monads through analogy, which really doesn’t work. The tutorials got so bad someone wrote an article complaining about them and unintentionally created a meme that monads are like burritos.<br>Here’s what a monad is, as far as a programmer (not a mathematician) is concerned, in some Rust-like pseudo code:<br>trait Monad {<br>fn new(v: A) -> M;<br>fn and_then(m: M, f: fn(A) -> M) -> M;
That’s it, that’s all it is. The names of the two functions vary, “new” is sometimes called “pure” or “return”2, while “and_then” is often called “bind” or “flat_map”. This is a higher-kinded type, meaning that it is generic over a type constructor (the M above), something most languages can’t natively express3.<br>The important part is that “and_then” function, the other function just means there’s a way to construct the type. The “and_then” function lets you sequence callbacks that stay “trapped within the monad”, so to speak. What’s that useful for you ask? Well it appears in many different contexts:<br>Rust’s Option is a Monad: here’s and_then. Lets you sequence callbacks that work with values if there are any and abort early if one is missing.
C#’s Task is a Monad: and_then is ContinueWith. Lets you sequence asynchronous callbacks. Promises in JavaScript are the mainstream example.
Java’s Stream is a Monad: and_then is flatMap. Lets you sequence callbacks that produce streams of values into producing one big stream.
If you have never used these functions/methods, don’t worry too much about it, they aren’t that useful most of the time, but now you know the pattern.<br>What the “Monad trait/typeclass/protocol/interface” makes possible is to write code that is generic over all of these different types.<br>But why on earth would you want to generalize over these very distinct types?
For most programmers the fact that these types share a common structure is utterly useless, I honestly struggle to think of any practical use case. BUT, for a language developer, it enables some neat tricks.<br>Haskell has something called do notation that works with any monad. It turns imperative-looking code into calls to “and_then” (which is called >>= in haskell):<br>If you use it with the Maybe/Either monads (Option/Result in Rust), you get behavior similar to checked exceptions or the ? operator, but not hardcoded into the language.
If you use it on MonadYield from the yield package, you get generators that aren’t hardcoded into the language.
If you use it on MonadAsync from the async package, you get async/await that isn’t hardcoded into the language.
But also logic programming, constraint programming, probabilistic programming, etc. Here’s an example of async in Haskell (from an async package test case):<br>async_poll = do
The async above is not a keyword, it is not built into the language. This is the async monad plus the do notation syntax sugar. It gets expanded to something like:<br>async_poll =<br>-- (\x -> foo) is how you make anonymous functions in haskell<br>async (threadDelay 1000000) >>= (\a -><br>poll a >>= (\r -><br>when (isJust r) (assertFailure "") >> (<br>poll a >>= (\r -><br>when (isJust r) (assertFailure "")))))
The >>= (bind/and_then) operators are sequencing the callbacks. The >> operator is just a wrapper that doesn’t pass along any value. This is similar to how async/await in JavaScript is essentially just syntax sugar for callback hell, but here the syntax sugar works for any type that defines the >>= operator.<br>F# has a similar feature in its Computation Expressions. It can’t represent the Monad concept as a type, so it just directly checks for a method called Bind (our friend and_then). As with Haskell’s do notation, this gets you checked exception-like early returns, async, generators, etc. with one language feature .<br>Isn’t this neat? A two method interface plus some rather trivial syntax sugar lets you implement all these other fancy language features as regular library code. Monads + “do notation” creates a sort of “uber language feature” that lets you implement other language features.<br>By implementing just one language feature, you get all those other fancy language features “ for free” , even ones you haven’t thought of yet.<br>Sadly, however, you don’t get their combinations for free.<br>Monads don’t compose very well by default. If you want to mix async with exceptions, you either need to implement the combined Monad manually, or you need to use something called Monad Transformers . The simplicity is not so simple...