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