Maybe

From the previous posts, you might gotten the impression that all monads require some kind of container to hold values. That’s not strictly true.

Implied container

Consider the m-result of the maybe-m monad

(defn m-result [x]
   x)

Yep. That’s nothing other than ‘identity’. This implies several things. First, any Clojure value is a monadic value for the maybe monad. Second, any Clojure function is a monadic function. And third, all the magic for this monad has to reside in m-bind.

(defn m-bind [mv mf]
   (if (nil? mv)
      nil
      (mf mv)))

What we see here is that if the value passed to m-bind is nil, then the monadic function is not executed and the return value is nil. This form of the maybe monad does not strictly fulfill the monad laws, but the transgression is minor and this monad is pretty useful as is. Checkout this post for more details.

The reason for this monad is more clearly seen using the domonad notation.

(domonad maybe-m
   [x (task1)
    y (task2)]
   (do-something-with x y))

If either task1 or task2 return nil, the result of this expression is nil. Only if x and y are both non-nil does do-something-with get executed. This is a better alternative to nesting when-let’s like:

(when-let [x (task1)]
   (when-let [y (task2)]
      (do-something-with x y)))

More power

They maybe monad also gives you something else; the ability to code alternatives, i.e. flow control.

Where a basic monad only needs to have m-result and m-bind specified, some monads also have m-zero and m-plus defined. For the maybe monad these are defined as:

(def m-zero nil)

(defn m-plus [& mvs]
   (first (drop-while nil? mvs)))

Notice that m-zero isn’t a function, it’s a static value. Also, notice that m-plus takes a variable number of monadic values and returns the first one that isn’t nil, or the m-zero value. There are additional monad laws m-zero and m-plus must satisfy.

Firstly, m-zero must interact with m-bind in the following way.

(m-bind m-zero f) produces m-zero
(m-bind mv (fn [x] m-zero)) produces m-zero

Secondly, m-zero and m-plus must obey the following.

(m-plus mv m-zero) produces mv
(m-plus m-zero mv) produces mv

The maybe monad isn’t the only one with m-zero and m-plus. Other monads that have m-zero and m-plus are sequence-m and set-m, while state-m doesn’t have those defined.

For sequence-m:

(def m-zero [])
(defn m-plus [& mvs)
   (apply concat mvs))

And for set-m:

(def m-zero #{})
(defn m-plus [& mvs]
   (apply union mvs))

Alternatives

So how are these used in domonad expressions? By using the :when clause.

(domonad maybe-m
	[x (some-fn)
	 :when (and (integer? x)
	            (odd? x))
	 y (* 2 x)]
	[x y])

So only when some-fn returns an odd integer does this expression return an answer. A better example is seen using sequence-m.

(domonad sequence-m
   [x (range 0 10)
    :when (odd? x)]
   [x (* 2 x)])

=> ([1 2] [3 6] [5 10] [7 14] [9 18])

Which is the same as using a for statement.

And with that, we finally have enough of a foundation in monads to start talking about DSL’s.

Jim Duey 13 February 2012
blog comments powered by Disqus