Friday, 10 January 2014

Monads and Cyrus

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:


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. :-)

1 comment:

  1. Your notation in FP would be `f5 . f4 . f3 . f2 . f1`, or as a concatenative PL `f1 f2 f3 f4 f5`.

    Anyhow, you seem to be focusing on the state monad use case. Monads are also useful in support of effects, backtracking models, dependency injection, and elsewhere.