3.15 Monads and Do Notation
monad-do provides a generic, specializable DSL for handling monadic values, inspired by Haskell’s do notation and Scala’s for comprehensions. monad-do itself is generic, expecting the provision of functions for the bind (>>=), return, and guard operators, but individual types can easily layer over this with a simple macro to provide a specialized version of the DSL for a particular data type.
syntax
(monad-do (bind return guard) exprs ... final-expr)
exprs = (name <- val) | (name = val) | (if test) | (expr ...) final-expr = (yield val ...) | (return-expr ...)
bind: A function which takes two arguments: an instance of the type, and a function. bind returns the result of applying the function to the value of the instance.
return: A function which takes a value, and wraps it in an instance of the type.
guard: A function which takes a boolean, and on true returns an instance of the type, and on false returns the empty instance or Null.
The rest of the body of the form is composed of various operations, which bind, guard, or return values, described as follows. The last line of the do notation is special, in a sense, as it must consist of either yield or a bare expression.
(name <- val) Binds val to name. val must be an instance of the type over which the do form operates.
(name = val) Wraps val in the current type, and binds it to name.
(if test) Filters the ongoing expression according to test.
(yield val ...) When used as the last line of a do form, returns the given val(s) wrapped in the type of the ongoing do form.
(expr ...) When used in the body of a do form, the expr is evaluated but its return value ignored. If the last line of the do form is a bare expression, then the form will return the result of the expression.
syntax
(maybe-do expr ...)
syntax
(list-do expr ...)
> (list-do (rank <- (append (range 2 to 10) '(J Q K A))) (suit <- '(♠ ♣ ♥ ♦)) (if (equal? suit '♦)) (card = (format$ "#_#_" rank suit)) (yield card)) '("2♦" "3♦" "4♦" "5♦" "6♦" "7♦" "8♦" "9♦" "10♦" "J♦" "Q♦" "K♦" "A♦")
syntax
(id-do expr ...)
syntax
(hole-do expr ...)
3.15.1 Implementing a monad
A "monad" is a data type which can contain a value, and a set of operator functions which operate on that type while obeying certain rules. You can think of them as a kind of container, and the components of an assembly line that processes the container and its contents.
> (do (describe Box (val Null)) (def fn box-return (val) (Box (list val))) (def fn box-bind (box fn) (fn (box 'val))) (def fn box-guard (test) (if test then (box-return Null) else Null)) (def macroset box-do [(_ e ...) (monad-do (box-bind box-return box-guard) e ...)]))
> (box-do (a <- (box-return 5)) (print a) (a <- (box-return 10)) (b = (* a 5)) (print b))
5
50