view src/clojure/contrib/test_contrib/monads/examples.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
line wrap: on
line source
1 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
3 ;;
4 ;; Monad application examples
5 ;;
6 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
7 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
9 (ns
10 #^{:author "Konrad Hinsen"
11 :skip-wiki true
12 :doc "Examples for using monads"}
13 clojure.contrib.monads.examples
14 (:use [clojure.contrib.monads
15 :only (domonad with-monad m-lift m-seq m-reduce m-when
16 sequence-m
17 maybe-m
18 state-m fetch-state set-state
19 writer-m write
20 cont-m run-cont call-cc
21 maybe-t)])
22 (:require (clojure.contrib [accumulators :as accu])))
24 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
25 ;;
26 ;; Sequence manipulations with the sequence monad
27 ;;
28 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
30 ; Note: in the Haskell world, this monad is called the list monad.
31 ; The Clojure equivalent to Haskell's lists are (possibly lazy)
32 ; sequences. This is why I call this monad "sequence". All sequences
33 ; created by sequence monad operations are lazy.
35 ; Monad comprehensions in the sequence monad work exactly the same
36 ; as Clojure's 'for' construct, except that :while clauses are not
37 ; available.
38 (domonad sequence-m
39 [x (range 5)
40 y (range 3)]
41 (+ x y))
43 ; Inside a with-monad block, domonad is used without the monad name.
44 (with-monad sequence-m
45 (domonad
46 [x (range 5)
47 y (range 3)]
48 (+ x y)))
50 ; Conditions are written with :when, as in Clojure's for form:
51 (domonad sequence-m
52 [x (range 5)
53 y (range (+ 1 x))
54 :when (= (+ x y) 2)]
55 (list x y))
57 ; :let is also supported like in for:
58 (domonad sequence-m
59 [x (range 5)
60 y (range (+ 1 x))
61 :let [sum (+ x y)
62 diff (- x y)]
63 :when (= sum 2)]
64 (list diff))
66 ; An example of a sequence function defined in terms of a lift operation.
67 (with-monad sequence-m
68 (defn pairs [xs]
69 ((m-lift 2 #(list %1 %2)) xs xs)))
71 (pairs (range 5))
73 ; Another way to define pairs is through the m-seq operation. It takes
74 ; a sequence of monadic values and returns a monadic value containing
75 ; the sequence of the underlying values, obtained from chaining together
76 ; from left to right the monadic values in the sequence.
77 (with-monad sequence-m
78 (defn pairs [xs]
79 (m-seq (list xs xs))))
81 (pairs (range 5))
83 ; This definition suggests a generalization:
84 (with-monad sequence-m
85 (defn ntuples [n xs]
86 (m-seq (replicate n xs))))
88 (ntuples 2 (range 5))
89 (ntuples 3 (range 5))
91 ; Lift operations can also be used inside a monad comprehension:
92 (domonad sequence-m
93 [x ((m-lift 1 (partial * 2)) (range 5))
94 y (range 2)]
95 [x y])
97 ; The m-plus operation does concatenation in the sequence monad.
98 (domonad sequence-m
99 [x ((m-lift 2 +) (range 5) (range 3))
100 y (m-plus (range 2) '(10 11))]
101 [x y])
104 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
105 ;;
106 ;; Handling failures with the maybe monad
107 ;;
108 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
110 ; Maybe monad versions of basic arithmetic
111 (with-monad maybe-m
112 (def m+ (m-lift 2 +))
113 (def m- (m-lift 2 -))
114 (def m* (m-lift 2 *)))
116 ; Division is special for two reasons: we can't call it m/ because that's
117 ; not a legal Clojure symbol, and we want it to fail if a division by zero
118 ; is attempted. It is best defined by a monad comprehension with a
119 ; :when clause:
120 (defn safe-div [x y]
121 (domonad maybe-m
122 [a x
123 b y
124 :when (not (zero? b))]
125 (/ a b)))
127 ; Now do some non-trivial computation with division
128 ; It fails for (1) x = 0, (2) y = 0 or (3) y = -x.
129 (with-monad maybe-m
130 (defn some-function [x y]
131 (let [one (m-result 1)]
132 (safe-div one (m+ (safe-div one (m-result x))
133 (safe-div one (m-result y)))))))
135 ; An example that doesn't fail:
136 (some-function 2 3)
137 ; And two that do fail, at different places:
138 (some-function 2 0)
139 (some-function 2 -2)
141 ; In the maybe monad, m-plus selects the first monadic value that
142 ; holds a valid value.
143 (with-monad maybe-m
144 (m-plus (some-function 2 0) (some-function 2 -2) (some-function 2 3)))
146 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
147 ;;
148 ;; Random numbers with the state monad
149 ;;
150 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
152 ; A state monad item represents a computation that changes a state and
153 ; returns a value. Its structure is a function that takes a state argument
154 ; and returns a two-item list containing the value and the updated state.
155 ; It is important to realize that everything you put into a state monad
156 ; expression is a state monad item (thus a function), and everything you
157 ; get out as well. A state monad does not perform a calculation, it
158 ; constructs a function that does the computation when called.
160 ; First, we define a simple random number generator with explicit state.
161 ; rng is a function of its state (an integer) that returns the
162 ; pseudo-random value derived from this state and the updated state
163 ; for the next iteration. This is exactly the structure of a state
164 ; monad item.
165 (defn rng [seed]
166 (let [m 259200
167 value (/ (float seed) (float m))
168 next (rem (+ 54773 (* 7141 seed)) m)]
169 [value next]))
171 ; We define a convenience function that creates an infinite lazy seq
172 ; of values obtained from iteratively applying a state monad value.
173 (defn value-seq [f seed]
174 (lazy-seq
175 (let [[value next] (f seed)]
176 (cons value (value-seq f next)))))
178 ; Next, we define basic statistics functions to check our random numbers
179 (defn sum [xs] (apply + xs))
180 (defn mean [xs] (/ (sum xs) (count xs)))
181 (defn variance [xs]
182 (let [m (mean xs)
183 sq #(* % %)]
184 (mean (for [x xs] (sq (- x m))))))
186 ; rng implements a uniform distribution in the interval [0., 1.), so
187 ; ideally, the mean would be 1/2 (0.5) and the variance 1/12 (0.8333).
188 (mean (take 1000 (value-seq rng 1)))
189 (variance (take 1000 (value-seq rng 1)))
191 ; We make use of the state monad to implement a simple (but often sufficient)
192 ; approximation to a Gaussian distribution: the sum of 12 random numbers
193 ; from rng's distribution, shifted by -6, has a distribution that is
194 ; approximately Gaussian with 0 mean and variance 1, by virtue of the central
195 ; limit theorem.
196 ; In the first version, we call rng 12 times explicitly and calculate the
197 ; shifted sum in a monad comprehension:
198 (def gaussian1
199 (domonad state-m
200 [x1 rng
201 x2 rng
202 x3 rng
203 x4 rng
204 x5 rng
205 x6 rng
206 x7 rng
207 x8 rng
208 x9 rng
209 x10 rng
210 x11 rng
211 x12 rng]
212 (- (+ x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12) 6.)))
214 ; Let's test it:
215 (mean (take 1000 (value-seq gaussian1 1)))
216 (variance (take 1000 (value-seq gaussian1 1)))
218 ; Of course, we'd rather have a loop construct for creating the 12
219 ; random numbers. This would be easy if we could define a summation
220 ; operation on random-number generators, which would then be used in
221 ; combination with reduce. The lift operation gives us exactly that.
222 ; More precisely, we need (m-lift 2 +), because we want both arguments
223 ; of + to be lifted to the state monad:
224 (def gaussian2
225 (domonad state-m
226 [sum12 (reduce (m-lift 2 +) (replicate 12 rng))]
227 (- sum12 6.)))
229 ; Such a reduction is often quite useful, so there's m-reduce predefined
230 ; to simplify it:
231 (def gaussian2
232 (domonad state-m
233 [sum12 (m-reduce + (replicate 12 rng))]
234 (- sum12 6.)))
236 ; The statistics should be strictly the same as above, as long as
237 ; we use the same seed:
238 (mean (take 1000 (value-seq gaussian2 1)))
239 (variance (take 1000 (value-seq gaussian2 1)))
241 ; We can also do the subtraction of 6 in a lifted function, and get rid
242 ; of the monad comprehension altogether:
243 (with-monad state-m
244 (def gaussian3
245 ((m-lift 1 #(- % 6.))
246 (m-reduce + (replicate 12 rng)))))
248 ; Again, the statistics are the same:
249 (mean (take 1000 (value-seq gaussian3 1)))
250 (variance (take 1000 (value-seq gaussian3 1)))
252 ; For a random point in two dimensions, we'd like a random number generator
253 ; that yields a list of two random numbers. The m-seq operation can easily
254 ; provide it:
255 (with-monad state-m
256 (def rng2 (m-seq (list rng rng))))
258 ; Let's test it:
259 (rng2 1)
261 ; fetch-state and get-state can be used to save the seed of the random
262 ; number generator and go back to that saved seed later on:
263 (def identical-random-seqs
264 (domonad state-m
265 [seed (fetch-state)
266 x1 rng
267 x2 rng
268 _ (set-state seed)
269 y1 rng
270 y2 rng]
271 (list [x1 x2] [y1 y2])))
273 (identical-random-seqs 1)
275 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
276 ;;
277 ;; Logging with the writer monad
278 ;;
279 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
281 ; A basic logging example
282 (domonad (writer-m accu/empty-string)
283 [x (m-result 1)
284 _ (write "first step\n")
285 y (m-result 2)
286 _ (write "second step\n")]
287 (+ x y))
289 ; For a more elaborate application, let's trace the recursive calls of
290 ; a naive implementation of a Fibonacci function. The starting point is:
291 (defn fib [n]
292 (if (< n 2)
293 n
294 (let [n1 (dec n)
295 n2 (dec n1)]
296 (+ (fib n1) (fib n2)))))
298 ; First we rewrite it to make every computational step explicit
299 ; in a let expression:
300 (defn fib [n]
301 (if (< n 2)
302 n
303 (let [n1 (dec n)
304 n2 (dec n1)
305 f1 (fib n1)
306 f2 (fib n2)]
307 (+ f1 f2))))
309 ; Next, we replace the let by a domonad in a writer monad that uses a
310 ; vector accumulator. We can then place calls to write in between the
311 ; steps, and obtain as a result both the return value of the function
312 ; and the accumulated trace values.
313 (with-monad (writer-m accu/empty-vector)
315 (defn fib-trace [n]
316 (if (< n 2)
317 (m-result n)
318 (domonad
319 [n1 (m-result (dec n))
320 n2 (m-result (dec n1))
321 f1 (fib-trace n1)
322 _ (write [n1 f1])
323 f2 (fib-trace n2)
324 _ (write [n2 f2])
325 ]
326 (+ f1 f2))))
328 )
330 (fib-trace 5)
332 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
333 ;;
334 ;; Sequences with undefined value: the maybe-t monad transformer
335 ;;
336 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
338 ; A monad transformer is a function that takes a monad argument and
339 ; returns a monad as its result. The resulting monad adds some
340 ; specific behaviour aspect to the input monad.
342 ; The simplest monad transformer is maybe-t. It adds the functionality
343 ; of the maybe monad (handling failures or undefined values) to any other
344 ; monad. We illustrate this by applying maybe-t to the sequence monad.
345 ; The result is an enhanced sequence monad in which undefined values
346 ; (represented by nil) are not subjected to any transformation, but
347 ; lead immediately to a nil result in the output.
349 ; First we define the combined monad:
350 (def seq-maybe-m (maybe-t sequence-m))
352 ; As a first illustration, we create a range of integers and replace
353 ; all even values by nil, using a simple when expression. We use this
354 ; sequence in a monad comprehension that yields (inc x). The result
355 ; is a sequence in which inc has been applied to all non-nil values,
356 ; whereas the nil values appear unmodified in the output:
357 (domonad seq-maybe-m
358 [x (for [n (range 10)] (when (odd? n) n))]
359 (inc x))
361 ; Next we repeat the definition of the function pairs (see above), but
362 ; using the seq-maybe monad:
363 (with-monad seq-maybe-m
364 (defn pairs-maybe [xs]
365 (m-seq (list xs xs))))
367 ; Applying this to a sequence containing nils yields the pairs of all
368 ; non-nil values interspersed with nils that result from any combination
369 ; in which one or both of the values is nil:
370 (pairs-maybe (for [n (range 5)] (when (odd? n) n)))
372 ; It is important to realize that undefined values (nil) are not eliminated
373 ; from the iterations. They are simply not passed on to any operations.
374 ; The outcome of any function applied to arguments of which at least one
375 ; is nil is supposed to be nil as well, and the function is never called.
378 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
379 ;;
380 ;; Continuation-passing style in the cont monad
381 ;;
382 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
384 ; A simple computation performed in continuation-passing style.
385 ; (m-result 1) returns a function that, when called with a single
386 ; argument f, calls (f 1). The result of the domonad-computation is
387 ; a function that behaves in the same way, passing 3 to its function
388 ; argument. run-cont executes a continuation by calling it on identity.
389 (run-cont
390 (domonad cont-m
391 [x (m-result 1)
392 y (m-result 2)]
393 (+ x y)))
395 ; Let's capture a continuation using call-cc. We store it in a global
396 ; variable so that we can do with it whatever we want. The computation
397 ; is the same one as in the first example, but it has the side effect
398 ; of storing the continuation at (m-result 2).
399 (def continuation nil)
401 (run-cont
402 (domonad cont-m
403 [x (m-result 1)
404 y (call-cc (fn [c] (def continuation c) (c 2)))]
405 (+ x y)))
407 ; Now we can call the continuation with whatever argument we want. The
408 ; supplied argument takes the place of 2 in the above computation:
409 (run-cont (continuation 5))
410 (run-cont (continuation 42))
411 (run-cont (continuation -1))
413 ; Next, a function that illustrates how a captured continuation can be
414 ; used as an "emergency exit" out of a computation:
415 (defn sqrt-as-str [x]
416 (call-cc
417 (fn [k]
418 (domonad cont-m
419 [_ (m-when (< x 0) (k (str "negative argument " x)))]
420 (str (. Math sqrt x))))))
422 (run-cont (sqrt-as-str 2))
423 (run-cont (sqrt-as-str -2))
425 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;