Skip to content

Three Useful Monads

Dag Brattli edited this page Apr 11, 2015 · 11 revisions

This is a translation of Three Useful Monads from Haskell into Python. Hopefully this should make the article much easier to understand for people who don't know Haskell. All code samples uses the Python OSlash library (Python 3 only).

How to make the examples work with Python:

$ pip3 install oslash
python3
>>> from oslash import *

Note: before reading this, you should know what a monad is. Read this post if you don’t! Here’s a function half:

In Python:

>>> half = lambda x: x//2

And we can apply it a couple of times:

>>> half(half(8))
2

Or we can compose the functions together to a new function:

>>> from oslash.util import compose
>>> quarter = compose(half, half)
>>> quarter(8)
2

Everything works as expected. Now you decide that you want to log what happens in this function:

>>> half = lambda x: (x//2, "I just halved %d!" % x)

Okay, fine. Now what if you want to apply half a couple of times?

Spoilers: it doesn’t happen automatically. You have to do it yourself:

>>> final_value = (
...    lambda v1, l1: (
...        lambda v2, l2: (v2, l1+l2))(*half(v1))
...    )(*half(8))

Yuck! That’s nowhere as nice as:

>>> half(half(8))

And what if you have more functions that log things? There’s a pattern here: for each function that returns a log along with a value, we want to combine those logs. This is a side-effect, and monads are great at side effects!

The Writer monad

Writer monad arriving on a horse

The Writer monad is cool. “Hey dude, I’ll handle the logging,” says Writer. “Go back to your clean code and crank up some Zeppelin!” Every writer has a log and a return value:

class Writer(Monad, Functor):

    """The writer monad."""
    ...

Writer lets us write code like this:

>> half(8) | half

Or you can use the Monad compose function, which does function composition with monads, to get:

>>> quarter = Monad.compose(half, half)
>>> quarter(8)
2 :: I just halved 8!I just halved 4!

which is pretty darn close to compose(half, half) that we used before. Cool! You use tell to write something to the log. And unit puts a value in a Writer. Here’s our new half function:

>>> unit, tell = Writer.unit, MonadWriter.tell
>>>
>>> half = lambda x: tell("I just halved %s!" % x).bind(lambda _: unit(x//2))

It returns a Writer:

And we can use the run method to extract the values from the Writer:

>>> half(8).run()
(4, 'I just halved 8!')

But the cool part is, now we can chain calls to half with |:

>>> quarter = half(8) | half
>>> quarter.run()
(2, 'I just halved 8!I just halved 4!')

Here’s what’s happening:

| (>>= in Haskell) magically knows how to combine two writers, so we don’t have to write any of that tedious code ourselves! Here’s the full definition of |:

Which is the same boilerplate code we had written before. Except now, | takes care of it for us. Cool! We also used unit ('return' in Haskell), which takes a value and puts it in a monad:

Writer.unit(val) = Writer(val, "")

(Note: these definitions are almost right. The real Writer monad allows us to use any Monoid as the log, not just strings. I have simplified it here a bit). Thanks, Writer monad!

The Reader Monad

Suppose you want to pass some config around to a lot of functions. Use the Reader monad:

The reader monad lets you pass a value to all your functions behind the scenes. For example:

unit = Reader.unit
ask = MonadReader.ask

greeter = ask() | (lambda name: unit("Hello, %s!" % name))

greeter returns a Reader monad:

Here’s how Reader is defined:

class Reader(Monad, Applicative, Functor):
    ...

Reader was always the renegade. The wild card. Reader is different because it’s only field is a function, and this is confusing to look at. But we both understand that you can use run (runReaderin Haskell) to get that function:

And then you give this function some state, and it’s used in greeter:

>>> greeter.run()("adit")
'Hello, adit!'

So when you use | (>>=in Haskell), you should get a Reader back. When you pass in a state to that reader, it should be passed through to every function in that monad.

m | k = Reader(lambda r: (k(m.run(r))).run(r))

Reader always was a little complex. The complex ones are the best. unit(return in Haskell) puts a value in a Reader:

unit(value) = Reader(lambda _: value)

And finally, ask gives you back the state that was passed in:

ask() = Reader(lambda x: x)

Want to spend some more time with Reader? Turn up the punk rock and see this longer example.

The State Monad

The State monad is the Reader monad’s more impressionable best friend:

She’s exactly like the Reader monad, except you can write as well as read! Here’s how State is defined:

class State(Monad, Functor):
    ...

You can get the state with get, and change it with put. Here’s an example:

>>> def greeter():
...     state = get().bind(lambda name:
...                        put("tintin").bind(
...                            lambda _: unit("hello, %s!" % name)))
...     return state
...
>>> greeter().run("adit")
(hello, adit!, "tintin")

Nice! Reader was all like “you won’t change me”, but State is committed to this relationship and willing to change. The definitions for the State monad look pretty similar to the definitions for the Reader monad: unit:

    @classmethod
    def unit(cls, value: Any) -> 'State':
        return cls(lambda state: (value, state))

bind aka | (>>=in Haskell):

   def bind(self, fn: Callable[[Any], 'State']) -> 'State':
        def _(result, state):
            return fn(result).run(state)
        return State(lambda state: _(*self.run(state)))

Writer. Reader. State. You added three powerful weapons to your Haskell arsenal today. Use them wisely.

References