You are on page 1of 10

monad.

scm Fri Feb 25 11:42:10 2011 1

;;;; Monads in Scheme

;;; Monads are a set of elegant design patterns


;;; for imposing predictable order in a pure
;;; functional program. (CPS is another way to
;;; accomplish this goal.) One may need to impose
;;; a predictable order to operate I/O or to
;;; simulate state with a functional "store"
;;; carried around in the program.

;;; Monads also provide plumbing for carrying the


;;; store in a way that does not impose itself on
;;; the users code, so that the result looks very
;;; much like a stateful program.

;;; A traditional example is to produce a binary


;;; tree isomorphic to a given one, but with the
;;; leaves replaced by a sequence of numbers in a
;;; left-to-right depth-first order. Here I
;;; investigate various ways to do this, starting
;;; with the most traditional use of assignment on
;;; a free variable.
monad.scm Fri Feb 25 11:42:10 2011 2

(define make-tree cons)


(define left car)
(define right cdr)
(define (terminal? x) (not (pair? x)))

;;; Statefully, with a free variable. This is the


;;; "natural" way to do the job in an imperative
;;; language.

(define (enumerate-tree tree)


(let ((n 0))
(define (walk tree)
(if (terminal? tree)
(let ((old-n n))
(set! n (+ n 1))
old-n)
(let* ((lt (walk (left tree)))
(rt (walk (right tree))))
(make-tree lt rt))))
(walk tree)))

#|
(enumerate-tree (a (b . c) . d))
;Value: (0 (1 . 2) . 3)
|#
monad.scm Fri Feb 25 11:42:10 2011 3

;;; Alternatively, one can use an auxiliary data


;;; structure to carry the state. This requires
;;; constructing and destructuring at each step.
;;; (This can be prettified with pattern-matching
;;; destructuring.)

(define (enumerate-tree tree)


(define (walk tree n)
(if (terminal? tree)
(cons n (+ n 1))
(let* ((ltn (walk (left tree) n))
(lt (car ltn)) (ln (cdr ltn))
(rtn (walk (right tree) ln))
(rt (car rtn)) (rn (cdr rtn)))
(cons (make-tree lt rt) rn))))
(car (walk tree 0)))

#|
(enumerate-tree (a (b . c) . d))
;Value: (0 (1 . 2) . 3)
|#
monad.scm Fri Feb 25 11:42:10 2011 4

;;; GJSs natural style, using continuation


;;; passing to eliminate the extra data structure.

(define (enumerate-tree tree)


(define (walk tree n cont)
(if (terminal? tree)
(cont n (+ n 1))
(walk (left tree)
n
(lambda (lt n)
(walk (right tree)
n
(lambda (rt n)
(cont (make-tree lt rt)
n)))))))
(walk tree 0 (lambda (tree n) tree)))

#|
(enumerate-tree (a (b . c) . d))
;Value: (0 (1 . 2) . 3)
|#
monad.scm Fri Feb 25 11:42:10 2011 5

;;; Notice that we must carry around that ugly


;;; "n", the state that was so neatly packaged in
;;; the imperative version. Is there some way we
;;; can retain the pure functional form but pipe
;;; it around, without having it clutter up our
;;; algorithm? The answer is yes. We set up a
;;; monad. Lets first do it with the auxiliary
;;; data structure.
monad.scm Fri Feb 25 11:42:10 2011 6

(define (return val)


(lambda (state)
(cons val state)))

(define (pipe first then) ;bind


(lambda (state)
(let* ((fvs (first state))
(first-val (car fvs))
(next-state (cdr fvs)))
((then first-val) next-state))))

(define (get)
(lambda (state)
(cons state state)))

(define (set new-state)


(lambda (old-state)
(cons #f new-state)))

(define (sequentially first then)


(lambda (state)
(let* ((fvs (first state))
(first-val (car fvs))
(next-state (cdr fvs)))
(then next-state))))

(define (run c initial-state)


(let* ((cvs (c initial-state))
(c-val (car cvs))
(next-state (cdr cvs)))
c-val))
monad.scm Fri Feb 25 11:42:10 2011 7

;;; And here is the enumerator using this


;;; plumbing. Notice that we have gotten rid of
;;; that annoying intrusion of the state into the
;;; non-terminal case of the walker.

(define (enumerate-tree tree)


(define (walk tree)
(if (terminal? tree)
(pipe (get)
(lambda (n)
(sequentially (set (+ n 1))
(return n))))
(pipe (walk (left tree))
(lambda (lt)
(pipe (walk (right tree))
(lambda (rt)
(return
(make-tree lt rt))))))))
(run (walk tree) 0))

(enumerate-tree (a (b . c) . d))
;Value: (0 (1 . 2) . 3)
monad.scm Fri Feb 25 11:42:10 2011 8

;;; Alternatively, we can get rid of the explicit


;;; construction of the value and state, using a
;;; continuation function:

(define (return val)


(lambda (state)
(lambda (cont)
(cont val state))))

(define (pipe first then) ;bind


(lambda (state)
((first state)
(lambda (first-val next-state)
((then first-val) next-state)))))

(define (get)
(lambda (state)
(lambda (cont)
(cont state state))))

(define (set new-state)


(lambda (old-state)
(lambda (cont)
(cont #f new-state))))

(define (sequentially first then)


(lambda (state)
((first state)
(lambda (first-val next-state)
(then next-state)))))

(define (run c initial-state)


((c initial-state)
(lambda (val next-state) val)))
monad.scm Fri Feb 25 11:42:10 2011 9

#|
;;; Monad laws, proved by symbolic evaluation.
;;; Monadic object above is : state -> (cont -> b)

;;; return : a -> M a


;;; pipe : M a -> ( (a -> M b) -> M b )

;;; We need to setup some objects

(define m ; m is a monadic object


(lambda (state)
(lambda (cont)
(cont b (list ms state)))))

(define f ; f : x -> M fx
(lambda (x)
(lambda (state)
(lambda (cont)
(cont (list f x)
(list fs state))))))

(define g ; g : x -> M gx
(lambda (x)
(lambda (state)
(lambda (cont)
(cont (list g x)
(list gs state))))))
monad.scm Fri Feb 25 11:42:10 2011 10

;;; Law 1: (pipe (return x) f) = (f x)

(equal? (run (pipe (return x) f) a)


(run (f x) a))
;Value: #t

;;; Law 2: (pipe f return) = f

(equal? (run (pipe f return) a)


(run (f a) uninteresting))
;Value: #t

;;; Law 3: (pipe (pipe m f) g)


;;; = (pipe m
;;; (lambda (x)
;;; (pipe (f x) g)))

(equal? (run (pipe (pipe m f) g) a)


(run (pipe m
(lambda (y)
(pipe (f y) g)))
a))
;Value: #t

You might also like