Monad; that’s a wrap!

Just like everybody else who starts to look at monads, I found it was like coming to the face of a sheer cliff. Let me qualify that: just like every other programmer who is not a mathematician (and that’s most of us). I am looking at monads in the context of clojure, so code snippets will generally be written in clojure-like syntax.

Adam’s talk eschews category theory, and mathematics in general, concentrating on how to define and use monads in code. This is an essential approach for the rest of us. Unfortunately, all of the terms used in discussing monads hark back to the mathematics, and that, I believe, makes the practical use of monads more confusing than it need be. This point was brought home to me strongly when, after observing what some of the others were saying, I watched the first part of a lecture by this extraordinary woman.

Warning: take everything I say with a large grain of salt. I am writing this to help me to sort it out in my own mind, and there are no guarantees that any of it is correct.

In all of the discussions I have seen, it is stressed that monads are defined in terms of the behaviour of two functions.

wrap

Although it is not usually mentioned first, I will start with the function that I will call wrap. Nobody else calls it that, but that’s what it does. It goes by a number of aliases:

result, m_result, mResult, etc.

return, m_return, etc.

lift, m_lift, etc.

the monadic function (? Sometimes. However, the 2nd argument to rewrap —see below— is generally called monadic function, so it may be more accurate to describe wrap as the base function on which all other monadic functions of a given monad are built.)

When he discusses the Array Monad for Javascript, Santosh Rajan gives the following signature for the monadic function, i.e. wrap.

f: T -> [T]

That is, for the Array Monad, wrap takes a value of some type T, and wraps it in an array. Both the type of value and the mode of wrapping are specific to each defined monad, but once defined, they are fixed for that monad.

The resulting, wrapped, value returned from wrap is known as a monadic value, and is often represented in code as mv. In this discussion, I’ll call it wrapt, for obvious reasons. I’ll call the argument to wrap the basic value, or bv.

(unwrap)

unwrap is under wraps, so to speak, because it is not part of the public face of monads. It is NOT one of the two functions which define the behaviour of a monad. It is, however, essential to the functioning of monads, and some kind of unwrap functionality must be available to the other function in the definition of monad: rewrap.

unwrap is the inverse of wrap, not surprisingly. In terms of Santosh’s Array monad, it’s signature might look like this.

f: T <- [T]

That is, unwrap takes a wrapt (monadic value), which is a wrapped basic value and returns the basic value.

rewrap

This the function that is generally called bind, m_bind, etc., although in Santosh’s examples, this function takes the name of the monad; for example, arrayMonad. The signature that Santosh gives for this function in the Array Monad is

M: [T] -> [T]

That is, it transforms a wrapt value to another wrapt value. In the case of the Array monad, the basic value is wrapped in an array.

The monadic function argument, mf, deserves a closer look. mf operates on a basic value to produce a new wrapt value. It is, in fact, a composition of functions. It composes wrap and some operation that modifies the basic value. So,

mf ⇒ (f′ ⋅ wrap)

where f′ is a function that modifies the basic value. In that scheme, wrap itself can be described as

wrap′ ⇒ (identity ⋅ wrap)

That given, we can now describe rewrap as

(defn rewrap [wrapt, (f′ ⋅ wrap)]

(let [bv (unwrap wrapt) new-wrapt (wrap (f′ bv))] new-wrapt))

or, equivalently,

(defn rewrap [wrapt, (f′ ⋅ wrap)]

(wrap (f′ (unwrap wrapt))))

The 3 R’s

Monads must obey three rules. These rules I have taken from Jim Duey’s post, with the appropriate translation to the “wrap” terminology I’m using here.

Rule 1

(rewrap (wrap x) f) ≡ (f x)

Alternatively, given our rewriting of rewrap, above, but using f rather than (f′ ⋅ wrap);