The M Word
This article continues my eclectic collection of recently-learned-topic reviews. Essentially, when I’m in class or work and come across a topic I find particularly difficult, counterintuitive, or just interesting, I’ll write one of these to hopefully ease the learning process for the next poor sucker who needs to learn the topic. Today, I’ll be discussing monads in a functional programming context.
Monads. For many developers just starting out with functional programming, this word inspires despair. For good cause: the concept of a monad can be incredibly confusing (it certainly was for me!) We see genius PhD types discussing the power of monads and praising them left and right, sometimes describing them using terms like “morphism”, “kleisli composition”, and “applicative”. The density of language used in discussing monads is in one part due to their origins in complex mathematics called category theory and in another part due to many of the people using them having very remarkable IQs and less remarkable social skills. As a response to this, the common approach to explaining monads is to “sneak up” on the concept, either saying “forget about the word Monad, think of a List” or building up a monad from its properties in a very particular context. While this is effective in getting the point across, the indirection makes it hard to explain later in an abstract context, like trying to define a word used in common slang. As Douglas Crockford said, “Once you … understand, ‘Oh, that’s what [a monad] is’, you lose the ability to explain it to anybody else”.
One quick note, I apologize for the weird Scala/C++ hybrid pseudocode dialect I seemed to invent in the course of writing this article
In attempt to break Crockford’s Monadic Curse, we’re going to approach monads directly. So, what is a monad? A monad is, as you’ll usually see it, a type with 2 functions: unit
(aka return
or point
) and flatMap
(aka bind
aka >>=
because haskell likes symbols).
unit
takes (something, doesn’t matter what) and returns a (monad) of (something). For instance, List (as a monad) has a unit
which takes a value and returns a List of that value’s type containing only that value. An implementation might look like the following:
def List::unit(x: T): List[T] = new List(x)
flatMap
takes a (something in a monad) and returns a (monad) of (something else in the same monad). For example, List (as a monad) has a flatMap which takes a List of values and a function turning that value type into another List. The flatMap then applies the function to each of the values in the list, returning the resultant lists concatenated together. In pseudocode, an implementation might look like:
def List[T].flatMap(funct: T => List[R]): List[R] = {
var list = new List[R]()
for (value in this) {
var result: List[R] = funct(value)
for (resultItem in result) list.append(resultItem)
}
return list
}
Or, if you’re comfortable with map and flatten:
def List[T].flatMap(funct: T => List[R]): List[R] =
this.map(funct).flatten
Furthermore, flatMap
and unit
have to be defined so that the following 3 laws hold:
Name | Law |
---|---|
Left identity | |
Right identity | |
Associativity |
(This table largely influenced by Trevor Hartman’s “Monad Laws in Scala”)
When you’re just using monads, (and you will almost exclusively be using them: it’s very rare to need to write your own), you don’t need to worry about the laws too much. So, for the developer looking to use monads day-to-day, all you need to know is flatMap
and unit
So, a review. unit
looks like T => M[T]
, and is used to wrap a value into a monad. flatMap
looks like (M[T]
).(T => M[U]) => M[U]
, and is applied to each value in a monad, with the results being combined to form a new monad. These two functions (plus 3 laws) allow for some crazy patterns and features, but they can be seen and used in contexts as simple as Lists.