Let me get one thing straight: I know absolutely nothing about monads. I have never intentionally used something I’ve recognised as a monad. I am dangerously unqualified to enhance your understanding of monads in any way. In fact reading this may damage you and prevent you from ever learning what a monad actually is!!!
The first reason I’m posting anything about monads at all is that I watched one of Robert “Uncle Bob” Martin’s entertaining NDC 2011 talks titled “WTF is a monad” (video available from the NDC site). I’m unsure how approximated or mathematically correct he was intending the presentation to be, but I found it really interesting and was able to implement something I can only hope was vaguely monadic based on my interpretation of the information he presented. So I thought I’d share it with you in case you could correct me (it should go without saying, but any mistakes here are mine and have nothing to do with Bob or his presentation). Worst case is it gets you interested enough to look into the topic and find out all the stuff I got wrong. (Was that alright OJ? ;))
The second reason is that I like writing words like monad, monadic, and monoid because for a brief, shining moment it makes me feel like a real computer scientist. This moment generally comes crashing down as soon as I realise I have no idea what any of these terms mean, but it is a good couple of milliseconds. :)
Did I mention I don’t know what I’m talking about? For this post especially I mean. Yes? Good, you should be safe to read on then…
Something something monad something
As far as I can gather, a monad is a structure that will let you use functions that take arguments of a certain type, and apply it to values from an another type (I’ll call this the monadic type, but I could be misusing the term). We need to be able to map back and forth between these types. Bob roughly approximates a monad to an adapter; a monad is a way of adapting one type to another.
It is the form of this adapter that makes it a monad. A monad can be expressed as two functions: the unit function, normally called
result, that takes an argument of the original type and returns the monadic type; and a
bind function that takes the monadic type and a function that works on original types. (Technically monads should also obey the monad laws. I’m sure I’ve missed other important points about them too, but let’s run with this for now.)
This structure has a few useful properties, mainly to do with being able to chain a sequence of functions that take arguments of the original type, then apply arguments of the monadic type to that chain. I think.
A dot monad?
Uncle Bob’s first example was using a monad to manipulate a dots type using functions that normally work with integers. The dots type is simply a representation of an integer using ‘.’ characters, so 5 maps to ‘…..’ and back again. We’d like to be able to be able to use dots with standard integer operations like
add, so that ‘..’ + ‘…’ gives ‘…..’.
Let’s look at an example in Python:
class DotMonad: def result(self, i): return '.' * i def bind(self, dots, f): return f(len(dots))
selfarguments to the functions is required due to how instance methods work in Python. You can safely ignore them for this post, but if you know C# or Java
selfbasically becomes like
thisin the context of an instance method.
result function just translates integers (
i) into dots. The
bind function takes some dots and a function
f that takes an integer. First it converts
dots to integers (using the length of the string of
dots) then calls
f using the result.
This means that if we have an
add function which takes integers, we can use our monad to adapt that function to take dots.
# Integer add function def add(a, b): return a+b # Monadic add function for dots def addM(dotsA, dotsB): m = DotMonad() return m.bind(dotsA, lambda a: m.bind(dotsB, lambda b: m.result(a+b) ) )
b as the plain integer types, and
dotsB to represent our monadic dots type. We can now call
addM('..', '...') and get
So how’s this work? Well remember that
bind takes a dot for a first argument, and calls the function provided as a second argument after converting the dot to an
int. The function we provide will be called with
dotsA converted to integer
a, then recursively call
bind to convert
dotsB in the same way. The last function in the chain is to the monad’s
result method which will convert the result of
a+b back to dots.
Let’s expand out and trace through the
addM('..', '...') example to make sure we’ve got a handle on this:
return m.bind(dotsA, # dotsA is '..', which is converted to int and passed to fn in 2nd arg lambda a: # bind calls function with a = len('..'), which is 2 m.bind(dotsB, # dotsB is '...', which is converted to int and passed to fn in 2nd arg lambda b: # 2nd bind calls function with b = len('...'), which is 3 m.result(a+b) # a+b is 2+3=5. m.result converts this back to '.....' )
I think this is called lifting the add (
+) function to work with our monad.
Lifting functions using monads
So we’ve now got a version of the basic integer
add function that can work with our monadic dots type. But we’d like to be able to apply all integer functions to work with dots. In fact, we can generalise our
addM function from before to lift any function which takes two arguments using a monad that can bind to that function’s argument type .
def liftm(m, op): return lambda a,b: m.bind(a, lambda ax: m.bind(b, lambda bx: m.result(op(ax, bx)) ) )
This is pretty much identical to our
addM function, but we can now do some neat stuff. Let’s import some standard Python operators and dot-erise them:
import operator addM = liftm(DotMonad(), operator.add) subM = liftm(DotMonad(), operator.sub) divM = liftm(DotMonad(), operator.div) mulM = liftm(DotMonad(), operator.mul) #Interactive python session >>> addM('..', '.') '...' >>> subM('....', '...') '.' >>> divM(mulM('..', '...'), subM('...', '.')) '...'
Should we try again? Maybe…
Let’s try another monad (again, from one Bob showed in his talk). This time we’re going to try and represent a type that can either have or be missing a value as a monadic type. So something very similar to .NET’s nullable types,
Nullable<T>. The difference with the monadic form is that, because of the way we chain sequences of
bind operations, we can actually perform operations involving missing values without throwing null reference exceptions everywhere.
class MaybeMonad: def result(self, x): return x def bind(self, maybe, f): if (maybe is None): return None else: return f(maybe)
result function just returns whatever value it is given. If it has a value it will return that value; otherwise it will return
Now we can lift our standard operators to work with our
>>> addm = liftm(MaybeMonad(), operator.add) >>> mulm = liftm(MaybeMonad(), operator.mul) >>> addm(2, 3) 5 >>> addm(4, None) >>> mulm(6, 7) 42 >>> mulm(None, None)
Or we can lift null-safe versions of other functions:
def string_lens(a, b): return len(a) + len(b) #Interactive python session >>> string_lens("Hello", "World") 10 >>> string_lens("Hello", None) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in string_lens TypeError: object of type 'NoneType' has no len() >>> safe = liftm(MaybeMonad(), string_lens) >>> safe("Hello", "World") 10 >>> safe("Hello", None)
string_lens throws when we pass in
None, but our
safe lifted version takes them in its stride.
Monads can actually be spotted out in the wild. They particularly enjoy frolicking with pure functional languages, where they can be used for (among other things) getting around the pesky limitation of not allowing side-effects in functions. Mutable state can be simulated by passing a State monad between functions. The I/O monad is used to encapsulate the side-effects of reading and writing from input and output.
Reading through the examples in the Wikipedia entry shows some collections can even be regarded as monads (for example,
result can return a list from a single item,
bind can map a function to each element in a list). In some instances LINQ statements can also be used as monads. I’ve even seen JQuery accused on monadishness (yes, I just made up a word).
So where’s this leave us? If you’re like me: dazed, confused, craving a cup of tea, and also quite eager to resume working through the excellent Learn you a Haskell tutorial. :)