…is the title of an article I thought I might have to write – provided I ever understood it myself. Fortunately, Noel Winstanley wrote it for me, and helped reinforce that my own understanding was moving in more less the right direction.
So what do I think monads are now?
I think they are ‘combinators’ – abstracted ways of combining things. They capture certain patterns of sticking results together, and, with a little syntactic sugar, make all the monad threading almost disappear. They are also a dirty trick (or beautiful hack, depending on your perspective) to let academics write real programs and still sleep at night:
This is Haskell’s answer to ‘state’ in a pure functional language. A function that does IO receives ‘the world’ and returns ‘the world’ – if you can contrive to pass the same ‘world’ to it will have the same predictable result. Of course, in implementation, ‘the world’ is a token that the run-time system uses to enforce safe, serial, execution of IO operations.
‘The world’ is wrapped up in the IO monad, so the process of passing it from function to function as you perform a series of IO operations can be coated in syntactic sugar and made a little less painful.
There is one catch though – the IO monad contains other data, specifically any data that depends on an IO operation (but sometimes nothing in particular, just because you are dealing with an IO function and it has to tied to the IO monad) The IO monad is also defined without the operation that lets you remove this data from the monad – once you have an IO monad, you can’t perform a non-IO operation that depends in any way it’s value; you have to keep passing IO monads around, thus maintaining the dependence of the computation on ‘the world’.
I’ve used the idea of combinators – though I doubt it counts as a monad. I’ve been working on the web reporting system. One of the things I have to do is build up partially dynamic SQL statements based on parameters, of which there might be any number, including none. Since the vast majority of ‘WHERE’ clauses are ‘AND’s, I built up a filter system that starts with “1=1″ (a dirty trick so you don’t have to conditionally include the ‘WHERE’ itself; another fun one is “1=0″ which gets you a cursor with structure but no rows) and builds up the conditional clauses from there.
If a clause builder (‘CustomerFilter’) gets an empty argument, it returns ‘PassFilter’, which is just ’1=1′. The filter combinator ignores these default filters, so you only actually get a ’1=1′ in the query if there were no parameters. Of course, I don’t have any syntactic sugar, so I have to chain together ‘AddFilter’s instead.