A question came up on the ThoughtWorks tech forum yesterday:
"What actually
is a monad, and why should I care?"
Various people had a go, or directed the OP to their favourite exposition. Not wanting to miss this opportunity to promote my
Cyrus language, I also replied:
____
This is just a
practical approximation to give you a feel for how I see the whats and
whys of Monads. Happy to take corrections if I've actually got it all
completely wrong. And apologies to anyone who feels patronised by my
jolly style or who is a Haskell programmer and whose teeth start to
grate..
A Pure Functional World
Say you love functions sooo much that you want everything to be a function in your programs. So that's:
output=f(input, input, ..).
Everywhere. And for the same inputs, you always get the same outputs.
Programs are built by chaining functions:
final-output = f1( f2(3), f3(4,"banana") )
No State?!?
First thing this means is no state
- you can't have persistent state. State as-in "value that something
takes at some point" can only be represented by those input numbers and
strings: 3, 4, "banana", and the outputs of functions.
So you can have "state", as long as it keeps being juggled transiently between the inputs and outputs of functions, maybe recursively.
But you really want
state, because the Real World has absolutely bloody tons of it.
Computers and programs that do real work have user interfaces and
databases.
So
you're just going to have to juggle and maintain those transient values
between functions, and do some recursion to keep the plates spinning.
If
only there were a way to make all this value juggling and plate
spinning easier. A functional programming pattern of some sort.. it
would have to have an academic, intimidating name, to put people off
thinking that State is Great, or anything like that.
Enter The Monad...
In fact, all you need to do is to juggle bigger, smarter values!
A Monad is just an aggregate, a container, a wrapper, a raiser-upper
of state which allows you to still be completely functional and yet
still pass around all the state you need in your entire program.
Indeed, to make the point: you could go really nuts and have a program like this, for your database:
end-of-day-database-state = f5( f4( f3( f2( f1(start-of-day-database-state)))))
where each fN() is a transaction, or a selector that carries forward the selections plus the entire database.
A better notation, perhaps for browser GUIs, would be:
f1(starting-DOM-state).f2().f3().f4().f5()
which can pass the DOM state through, along with, again, selections - a subset or working set. Look
familiar?
So, in
general, you want to apply little functions to little bits inside the
whole passed/juggled state. You never change the whole database or the
whole GUI at once. Perhaps just a field in the database or GUI. You may
have intermediate states that get added to the aggregate and passed on
to the next stage.
Actually, f1()
in both examples is special, because it takes a "simple" state or value
and raises it up to this wrapper/aggregate/super-value - it creates a
Monad. It's like $() in JQuery. Similarly f5()
is special, at least in the database example, as it goes back again to
the simple state. The simple state doesn't know about all this
aggregate, compounding stuff: it doesn't have the functions or methods
that juggle and spin plates.
Why
not just have explicit, big state objects instead of Monads, and only
use pure functions to transition those objects between states?
Exactly. It would be
way easier
than all this juggling and plate-spinning, and much easier to see what
was going on. You could give those state objects URLs, and everything.
:-)