Monads aren't as hard as you think
bytes.yingw787.com
bytes.yingw787.com
What I really like about the HN community is the notion of shipping even if you’re embarrassed by it, because the future good or bad is built by the people who show up.
> A monad is a data type (e.g. int) that encapsulates some control flow (e.g. try/catch).
But... I'm already lost. How would an integer "encapsulate" control flow? Like different integer values would represent different errors that could occur? Or you could use integers as a representation for a language that has try/catch as a feature? Even in this simplest case, if you don't already grok monads, it's somehow not illuminating for me. YMMV
A better sentence is probably
A monad is a datatype (e.g. Either<T,U>) that encapsulates some control flow (e.g. if T != null return T else U)
if I understand it correctly
Telling something "XXX is really useful and you've been putting off learning it because most of the material is inaccessible, so here's a gentle introduction to the concepts" does, though.
Ignoring the fact that `int` itself doesn't have much to do with monads (it's presumably just giving an example of a type), I've always disliked the notion of talking about monads being types or vice versa.
The way I think of it is that the monad itself consists of the monadic operations for some set of values. The "IO monad" consists of the `bind` and `unit` functions to do with values of types `IO<T>` for any `T` (eg, `IO<Int>`, `IO<String>`, `IO<IO<Int>>`). Similarly, the "Maybe monad" consists of the `bind` and `unit` functions to do with values of types `Maybe<T>` for any `T`.
Both of these monads can be said to implement a common interface, which might be expressed using the following Java-like code (using Java just to emphasise that there isn't really[0] anything magical here):
interface Monad<M> {
<T> M<T> unit(T v);
<T, U> M<U> bind(M<T> a, Function<T, M<U>> f);
}
If we were to define an "IO monad", we would expect it to implement `Monad<IO>`, therefore an "IO monad" is really just a value of type `Monad<IO>`: Monad<IO> ioMonad = new Monad<IO>() {
<T> IO<T> unit(T v) { .. }
<T, U> IO<U> bind(IO<T> a, Function<T, IO<U>> f) { .. }
};
This is actually pretty much exactly how it works in Idris for example, where `Monad IO` is itself a type, and you can actually ask for the `Monad IO` implementation by writing, funnily enough, `the (Monad IO) %implementation`: Idris> 4 + 5 -- to demonstrate the REPL
9 : Integer
Idris> Monad IO
Monad (IO' (MkFFI C_Types String String)) : Type
Idris> the (Monad IO) %implementation
constructor of Prelude.Monad.Monad (\meth, meth, meth, meth => io_bind meth meth) (\meth, meth => io_bind meth id) : Monad IO
[0] The only reason it doesn't actually work is because Java doesn't support higher-kinded polymorphism, so you can't pass type-level functions such as `IO` or `List` as type parameters, therefore you can't actually write `M<T>` as appears in the interface body.- Bartosz Milewski
But the juicy benefit to Promises is the (monad-based) transformation to async/await notation. Async/await notation gives you back the try { } catch { } synchronous-looking error catching that you are wishing for, all it takes is a couple new magic keywords and your code looks very similar to what it would in a magic synchronous world without promises. (Most current browsers and current versions of Node even directly support async/await today [0] directly out of the box, no need for a compiler/transpiler tool to do the transformation for you.)
async/await syntax lets you catch promise rejection exactly like you would any other type of JS exception with a try {} catch {}.
[0] https://developer.mozilla.org/en-US/docs/Web/API/Window/unha...
Except of course the whole point of monadic thinking appears to be "no side effects" so .. I'm stuck
Monads don't have anything to do with IO or avoiding side effects, they are about composing/combining operations. At heart, the monad laws basically state that if you have two puzzle pieces with similar edges you can snap them together and start thinking about them as a larger puzzle piece in the same puzzle, and that eventually when you are done snapping together pieces you can admire whatever image they form as a result.
The fact that people talk a lot about the IO Monad in Haskell was that it was a jigsaw puzzle that solved a lot of problems in early Haskell, and led to a lot of exploration in monads.
Let X be any set. Let mX be the set of formal linear combinations of elements of X (that is, expressions like 3x₁+2x₂ where x₁ and x₂ are elements of X). Then m is a monad.
fmap is variable substitution. Ex. fmap [x₁->y₁, x₂->y₂] (3x₁ + 2x₂) = (3y₁ + 2y₂).
join is simplification of nested expressions. Ex. join (3(2x₁) + 2(x₁ + 2x₂)) = (8x₁ + 4x₂).
>>= is substitution and simplification. Ex. (3x₁ + 2x₂) >>= [x₁->3y₂, x₁->-y₂] = (3(3y₂) + 2(-y₂)) = 7y₂.
A Kleisli arrow X->mY is a system of linear equations, ie. a matrix
x₁ = 3y₁ + 6y₂
x₂ = y₁ - 2y₂
>=> is composition of linear systems, ie. matrix multiplication.Whereas in IO you think of f >=> g as meaning "first f, then g", here f >=> g is the matrix product fg, ie. "first g, then f".
Googling, I found the following helpful comment:
"...while addition and multiplication are both monoids over the positive natural numbers, a monad is a monoid object in a category of endofunctors: return is the unit, and join is the binary operation. It couldn't be more simple. If that confuses you, it might be helpful to see a Monad as a lax functor from a terminal bicategory"
"A few months ago Brent Yorgey complained about a certain class of tutorials which present monads by explaining how monads are like burritos.
At first I thought the choice of burritos was only a facetious reference to the peculiar and sometimes strained analogies these tutorials make. But then I realized that monads are like burritos."