February 28, 2014
Monads tend to come up a lot in functional programming. All you need to know about functional programming for now is that it’s a paradigm in which mutable data is generally discouraged, or even forbidden. Computation generally happens by composing pure functions together. A pure function is one that does nothing but return the result of a computation. It doesn’t print anything, store anything, ask for user input, etc. It just manipulates the data you pass it and returns more data.
You may be more familiar with object-oriented programming. Let me translate monads into OOP-speak.
Monadis an interface, or an abstract class if you like. It has a constructor and a
class Monad: def __init__(self, x): raise NotImplementedError def bind(self, f): raise NotImplementedError
xcan be anything.
fis a function that takes something of the same type as
x(presumably from the monad) and returns a new monad. Lastly,
bindalso returns a new monad (often from the result of
f). Literally that’s it. Maybe you don’t know what it’s used for, but at least now you’ve seen the interface.
Typically, a monad acts as some sort of container, and
bindapplies a function to what it contains. Let’s make a monad that stores a single element:
class SimpleMonad(Monad): def __init__(self, x): self.x = x def bind(self, f): return f(self.x)
To make this a little more concrete, this is how you use it:
def double(x): return SimpleMonad(x * 2) def add1(x): return SimpleMonad(x + 1) foo = SimpleMonad(5).bind(double).bind(add1)
Okay, so you can chain functions together with
bind. But why would we do that? Equivalently, we could have just written
add1(double(5)), which is much shorter.
The main idea is that monads don’t have to just call the functions you pass to
bind. They can change the rules. For example, here is a monad that allows you to chain operations without worrying about null pointer errors.
class MaybeMonad(Monad): def __init__(self, x): self.x = x def bind(self, f): if self.x is None: return self else: return f(self.x)
If any intermediate computation in a sequence of operations results in
None, the entire expression becomes
Noneand no further operations are performed (much like how
NaNworks in floating-point arithmetic, if you’re familiar with that). If you spend more time with monads, you’ll find that you can also use them to implement exception handling, mutable state, concurrency, and a bunch of other things.
Remember: functional programming is all about writing programs by composing functions together. The power of monads is that they allow you to change the rules for how functions are composed.
When I was learning about monads, the main things I was confused about were:
- It seems like using monads requires you to write a lot of code, e.g.,
add1(double(5)). Do people really go through all this trouble?
- How do I get the value out of the monad? Can I access it like
- I hear that Haskell doesn’t let you write “impure” functions that have side effects, but I know for a fact that Haskell programs can print to the screen and write to files. What’s the deal? Do monads somehow resolve this?
Well, I know the answers now, so let me share the love:
- Haskell has a special syntax for monads and Python doesn’t—so that’s why they don’t look very nice in Python. It’s actually quite brilliant. Check it out if you have time. Using it feels very natural and it looks a lot like imperative code.
- Some monads provide a “getter” function that allows you to get the value back out. But a monad is not required to have one. For some monads, it doesn’t make sense to take a value out of it. A monad is not required to act like a container. See #3.
- Yep—all functions in Haskell are pure. Haskell has a neat trick for doing I/O. You can think of a Haskell program as a pure computation that returns a tree-like data structure, and this tree represents another program that does impure things.Now, building this tree can be done in a purely functional way—as is required in Haskell. But when you “run” a Haskell program, two things happen: 1) your pure Haskell computation is executed, resulting in this tree-like data structure, 2) the tree-program from step 1 (which can print to the screen and write to files) is also executed.It just so happens that a certain monad, especially when combined with Haskell’s special monad syntax, makes it easy to build this tree. This monad is called
IOin Haskell. The bind operation does not call the function you pass it, as it did in our monad examples above. Rather, it just stores it in the tree. The runtime will call it when interpreting the tree. That’s the high-level idea. In this post, I implement this idea in Python, which will probably make it a little more clear for you.Lastly, the
IOmonad does not have a “getter” function. For example, if you have an
IOaction that asks a user for a string of input, you cannot extract the string from the monad. This is because the
IOaction doesn’t actually have a string to give you—it only represents a computation that returns a string. When you
fto this monad, you’re building a new
IOaction that asks the user for a string and then calls
To summarize, monads let you change what it means to compose functions together. You might want to keep track of some extra information, store the functions in a tree, call them in a different order, call them multiple times, skip some functions entirely (based on the value being passed to them), etc.
You don’t have to use them, but now you don’t have to fear them either!
EDIT: Fixed a typo and added the
MaybeMonadexample at /u/wargotad’s suggestion.