Senko Rašić

Maybe Monad in Python

This post talks about a neat trick for simplifying program flow in Python. If you know Haskell, you’ll recognize it as the Maybe monad. If you’re more of a Scala or OCaml type of person, it’s an Option. If OOP and design patterns rock your boat, it looks eerily like the Null Object Pattern.

Here’s a problem to start with: imagine you have a function that deals with several variables. For example, it might do a calculation or perform some I/O based on the variables. One (or more) of them may be not supplied, not valid, unknown, or have to be similarly special-cased.

The naive code might look like:

foo = get_foo() # may return None if we can't get 'foo'
foo_squared = foo * foo
bar = ... # doesn't depend on foo
baz = foo_squared * bar
print foo, bar, baz

This doesn’t handle the fact that foo might not be known (ie. have the value of None here), in case the program will happily crash.

No worries, we’ll just add checks where appropriate, right?

foo = get_foo() # may return None if we can't get 'foo'

if foo is None:
    foo_squared = None
else:
    foo_squared = foo * foo

bar = ... # doesn't depend on foo

if foo_squared is None:
    baz = None
else:
    baz = foo_squared * bar

print foo, bar, baz

This works correctly (unless I made a mistake), but is ugly and the actual calculation we tried to do is hidden between the special-case checks. In this small example, the calculation can be reordered to simplify it a bit - finding more complex examples of the same problem in real-world code is left as an exercise for the reader.

Instead, let’s define something called Maybe, that can be either Nothing (which means, there’s no value of interest), or Just(value) if it does hold a useful value. Further more, let’s define that any operation that involves a Nothing immediately results in Nothing. Operations that involve a Just(value) will compute the result as usual, but then additionally wrap it again in Just, to produce Just(value).

The above function then looks something like:

foo = maybe_get_foo()  # returns  Just(<value>) or Nothing
foo_squared = foo * foo
bar = ... # doesn't depend on foo
baz = foo_squared * bar
print foo, bar, baz

Much better.

How hard it is to define such a construct in Python? As it turns out, not that hard. Here’s a complete implementation, with documentation, tests and a license, in less than 250 lines - maybe.py. It doesn’t cover all the operators possible (patches welcome), but it does cover most of the usual suspects.

Functional programming aficionados will probably balk both at the implementation and the usage. There’s objects, operator overloading, metaclasses and magic mocking of attributes and function calls. And stuff like this works:

>>> Just('hello')[:-1].upper()
Just('HELL')
>>> Just(Nothing)[:-1].upper()
Nothing

Is it really a monad, then? Yes, it is: the relevant Monad laws hold (see the docstrings). However, it doesn’t try to shoehorn Lisp, Haskell or Scala syntax into Python (if you’re into that, fn.py might be of interest). It uses Python’s strengths instead of awkwardly stepping around its “not really a functional programming language” limitations.

And that’s why it was fun to write.

Senko Rašić
Audio/Video and Real-Time Web consultant
Read more articles

Have a project I could help with?

Contact me