Mercurial > lasercutter
diff 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 diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/clojure/contrib/test_contrib/monads/examples.clj Sat Aug 21 06:25:44 2010 -0400 1.3 @@ -0,0 +1,425 @@ 1.4 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.5 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.6 +;; 1.7 +;; Monad application examples 1.8 +;; 1.9 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.10 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.11 + 1.12 +(ns 1.13 + #^{:author "Konrad Hinsen" 1.14 + :skip-wiki true 1.15 + :doc "Examples for using monads"} 1.16 + clojure.contrib.monads.examples 1.17 + (:use [clojure.contrib.monads 1.18 + :only (domonad with-monad m-lift m-seq m-reduce m-when 1.19 + sequence-m 1.20 + maybe-m 1.21 + state-m fetch-state set-state 1.22 + writer-m write 1.23 + cont-m run-cont call-cc 1.24 + maybe-t)]) 1.25 + (:require (clojure.contrib [accumulators :as accu]))) 1.26 + 1.27 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.28 +;; 1.29 +;; Sequence manipulations with the sequence monad 1.30 +;; 1.31 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.32 + 1.33 +; Note: in the Haskell world, this monad is called the list monad. 1.34 +; The Clojure equivalent to Haskell's lists are (possibly lazy) 1.35 +; sequences. This is why I call this monad "sequence". All sequences 1.36 +; created by sequence monad operations are lazy. 1.37 + 1.38 +; Monad comprehensions in the sequence monad work exactly the same 1.39 +; as Clojure's 'for' construct, except that :while clauses are not 1.40 +; available. 1.41 +(domonad sequence-m 1.42 + [x (range 5) 1.43 + y (range 3)] 1.44 + (+ x y)) 1.45 + 1.46 +; Inside a with-monad block, domonad is used without the monad name. 1.47 +(with-monad sequence-m 1.48 + (domonad 1.49 + [x (range 5) 1.50 + y (range 3)] 1.51 + (+ x y))) 1.52 + 1.53 +; Conditions are written with :when, as in Clojure's for form: 1.54 +(domonad sequence-m 1.55 + [x (range 5) 1.56 + y (range (+ 1 x)) 1.57 + :when (= (+ x y) 2)] 1.58 + (list x y)) 1.59 + 1.60 +; :let is also supported like in for: 1.61 +(domonad sequence-m 1.62 + [x (range 5) 1.63 + y (range (+ 1 x)) 1.64 + :let [sum (+ x y) 1.65 + diff (- x y)] 1.66 + :when (= sum 2)] 1.67 + (list diff)) 1.68 + 1.69 +; An example of a sequence function defined in terms of a lift operation. 1.70 +(with-monad sequence-m 1.71 + (defn pairs [xs] 1.72 + ((m-lift 2 #(list %1 %2)) xs xs))) 1.73 + 1.74 +(pairs (range 5)) 1.75 + 1.76 +; Another way to define pairs is through the m-seq operation. It takes 1.77 +; a sequence of monadic values and returns a monadic value containing 1.78 +; the sequence of the underlying values, obtained from chaining together 1.79 +; from left to right the monadic values in the sequence. 1.80 +(with-monad sequence-m 1.81 + (defn pairs [xs] 1.82 + (m-seq (list xs xs)))) 1.83 + 1.84 +(pairs (range 5)) 1.85 + 1.86 +; This definition suggests a generalization: 1.87 +(with-monad sequence-m 1.88 + (defn ntuples [n xs] 1.89 + (m-seq (replicate n xs)))) 1.90 + 1.91 +(ntuples 2 (range 5)) 1.92 +(ntuples 3 (range 5)) 1.93 + 1.94 +; Lift operations can also be used inside a monad comprehension: 1.95 +(domonad sequence-m 1.96 + [x ((m-lift 1 (partial * 2)) (range 5)) 1.97 + y (range 2)] 1.98 + [x y]) 1.99 + 1.100 +; The m-plus operation does concatenation in the sequence monad. 1.101 +(domonad sequence-m 1.102 + [x ((m-lift 2 +) (range 5) (range 3)) 1.103 + y (m-plus (range 2) '(10 11))] 1.104 + [x y]) 1.105 + 1.106 + 1.107 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.108 +;; 1.109 +;; Handling failures with the maybe monad 1.110 +;; 1.111 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.112 + 1.113 +; Maybe monad versions of basic arithmetic 1.114 +(with-monad maybe-m 1.115 + (def m+ (m-lift 2 +)) 1.116 + (def m- (m-lift 2 -)) 1.117 + (def m* (m-lift 2 *))) 1.118 + 1.119 +; Division is special for two reasons: we can't call it m/ because that's 1.120 +; not a legal Clojure symbol, and we want it to fail if a division by zero 1.121 +; is attempted. It is best defined by a monad comprehension with a 1.122 +; :when clause: 1.123 +(defn safe-div [x y] 1.124 + (domonad maybe-m 1.125 + [a x 1.126 + b y 1.127 + :when (not (zero? b))] 1.128 + (/ a b))) 1.129 + 1.130 +; Now do some non-trivial computation with division 1.131 +; It fails for (1) x = 0, (2) y = 0 or (3) y = -x. 1.132 +(with-monad maybe-m 1.133 + (defn some-function [x y] 1.134 + (let [one (m-result 1)] 1.135 + (safe-div one (m+ (safe-div one (m-result x)) 1.136 + (safe-div one (m-result y))))))) 1.137 + 1.138 +; An example that doesn't fail: 1.139 +(some-function 2 3) 1.140 +; And two that do fail, at different places: 1.141 +(some-function 2 0) 1.142 +(some-function 2 -2) 1.143 + 1.144 +; In the maybe monad, m-plus selects the first monadic value that 1.145 +; holds a valid value. 1.146 +(with-monad maybe-m 1.147 + (m-plus (some-function 2 0) (some-function 2 -2) (some-function 2 3))) 1.148 + 1.149 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.150 +;; 1.151 +;; Random numbers with the state monad 1.152 +;; 1.153 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.154 + 1.155 +; A state monad item represents a computation that changes a state and 1.156 +; returns a value. Its structure is a function that takes a state argument 1.157 +; and returns a two-item list containing the value and the updated state. 1.158 +; It is important to realize that everything you put into a state monad 1.159 +; expression is a state monad item (thus a function), and everything you 1.160 +; get out as well. A state monad does not perform a calculation, it 1.161 +; constructs a function that does the computation when called. 1.162 + 1.163 +; First, we define a simple random number generator with explicit state. 1.164 +; rng is a function of its state (an integer) that returns the 1.165 +; pseudo-random value derived from this state and the updated state 1.166 +; for the next iteration. This is exactly the structure of a state 1.167 +; monad item. 1.168 +(defn rng [seed] 1.169 + (let [m 259200 1.170 + value (/ (float seed) (float m)) 1.171 + next (rem (+ 54773 (* 7141 seed)) m)] 1.172 + [value next])) 1.173 + 1.174 +; We define a convenience function that creates an infinite lazy seq 1.175 +; of values obtained from iteratively applying a state monad value. 1.176 +(defn value-seq [f seed] 1.177 + (lazy-seq 1.178 + (let [[value next] (f seed)] 1.179 + (cons value (value-seq f next))))) 1.180 + 1.181 +; Next, we define basic statistics functions to check our random numbers 1.182 +(defn sum [xs] (apply + xs)) 1.183 +(defn mean [xs] (/ (sum xs) (count xs))) 1.184 +(defn variance [xs] 1.185 + (let [m (mean xs) 1.186 + sq #(* % %)] 1.187 + (mean (for [x xs] (sq (- x m)))))) 1.188 + 1.189 +; rng implements a uniform distribution in the interval [0., 1.), so 1.190 +; ideally, the mean would be 1/2 (0.5) and the variance 1/12 (0.8333). 1.191 +(mean (take 1000 (value-seq rng 1))) 1.192 +(variance (take 1000 (value-seq rng 1))) 1.193 + 1.194 +; We make use of the state monad to implement a simple (but often sufficient) 1.195 +; approximation to a Gaussian distribution: the sum of 12 random numbers 1.196 +; from rng's distribution, shifted by -6, has a distribution that is 1.197 +; approximately Gaussian with 0 mean and variance 1, by virtue of the central 1.198 +; limit theorem. 1.199 +; In the first version, we call rng 12 times explicitly and calculate the 1.200 +; shifted sum in a monad comprehension: 1.201 +(def gaussian1 1.202 + (domonad state-m 1.203 + [x1 rng 1.204 + x2 rng 1.205 + x3 rng 1.206 + x4 rng 1.207 + x5 rng 1.208 + x6 rng 1.209 + x7 rng 1.210 + x8 rng 1.211 + x9 rng 1.212 + x10 rng 1.213 + x11 rng 1.214 + x12 rng] 1.215 + (- (+ x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 x11 x12) 6.))) 1.216 + 1.217 +; Let's test it: 1.218 +(mean (take 1000 (value-seq gaussian1 1))) 1.219 +(variance (take 1000 (value-seq gaussian1 1))) 1.220 + 1.221 +; Of course, we'd rather have a loop construct for creating the 12 1.222 +; random numbers. This would be easy if we could define a summation 1.223 +; operation on random-number generators, which would then be used in 1.224 +; combination with reduce. The lift operation gives us exactly that. 1.225 +; More precisely, we need (m-lift 2 +), because we want both arguments 1.226 +; of + to be lifted to the state monad: 1.227 +(def gaussian2 1.228 + (domonad state-m 1.229 + [sum12 (reduce (m-lift 2 +) (replicate 12 rng))] 1.230 + (- sum12 6.))) 1.231 + 1.232 +; Such a reduction is often quite useful, so there's m-reduce predefined 1.233 +; to simplify it: 1.234 +(def gaussian2 1.235 + (domonad state-m 1.236 + [sum12 (m-reduce + (replicate 12 rng))] 1.237 + (- sum12 6.))) 1.238 + 1.239 +; The statistics should be strictly the same as above, as long as 1.240 +; we use the same seed: 1.241 +(mean (take 1000 (value-seq gaussian2 1))) 1.242 +(variance (take 1000 (value-seq gaussian2 1))) 1.243 + 1.244 +; We can also do the subtraction of 6 in a lifted function, and get rid 1.245 +; of the monad comprehension altogether: 1.246 +(with-monad state-m 1.247 + (def gaussian3 1.248 + ((m-lift 1 #(- % 6.)) 1.249 + (m-reduce + (replicate 12 rng))))) 1.250 + 1.251 +; Again, the statistics are the same: 1.252 +(mean (take 1000 (value-seq gaussian3 1))) 1.253 +(variance (take 1000 (value-seq gaussian3 1))) 1.254 + 1.255 +; For a random point in two dimensions, we'd like a random number generator 1.256 +; that yields a list of two random numbers. The m-seq operation can easily 1.257 +; provide it: 1.258 +(with-monad state-m 1.259 + (def rng2 (m-seq (list rng rng)))) 1.260 + 1.261 +; Let's test it: 1.262 +(rng2 1) 1.263 + 1.264 +; fetch-state and get-state can be used to save the seed of the random 1.265 +; number generator and go back to that saved seed later on: 1.266 +(def identical-random-seqs 1.267 + (domonad state-m 1.268 + [seed (fetch-state) 1.269 + x1 rng 1.270 + x2 rng 1.271 + _ (set-state seed) 1.272 + y1 rng 1.273 + y2 rng] 1.274 + (list [x1 x2] [y1 y2]))) 1.275 + 1.276 +(identical-random-seqs 1) 1.277 + 1.278 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.279 +;; 1.280 +;; Logging with the writer monad 1.281 +;; 1.282 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.283 + 1.284 +; A basic logging example 1.285 +(domonad (writer-m accu/empty-string) 1.286 + [x (m-result 1) 1.287 + _ (write "first step\n") 1.288 + y (m-result 2) 1.289 + _ (write "second step\n")] 1.290 + (+ x y)) 1.291 + 1.292 +; For a more elaborate application, let's trace the recursive calls of 1.293 +; a naive implementation of a Fibonacci function. The starting point is: 1.294 +(defn fib [n] 1.295 + (if (< n 2) 1.296 + n 1.297 + (let [n1 (dec n) 1.298 + n2 (dec n1)] 1.299 + (+ (fib n1) (fib n2))))) 1.300 + 1.301 +; First we rewrite it to make every computational step explicit 1.302 +; in a let expression: 1.303 +(defn fib [n] 1.304 + (if (< n 2) 1.305 + n 1.306 + (let [n1 (dec n) 1.307 + n2 (dec n1) 1.308 + f1 (fib n1) 1.309 + f2 (fib n2)] 1.310 + (+ f1 f2)))) 1.311 + 1.312 +; Next, we replace the let by a domonad in a writer monad that uses a 1.313 +; vector accumulator. We can then place calls to write in between the 1.314 +; steps, and obtain as a result both the return value of the function 1.315 +; and the accumulated trace values. 1.316 +(with-monad (writer-m accu/empty-vector) 1.317 + 1.318 + (defn fib-trace [n] 1.319 + (if (< n 2) 1.320 + (m-result n) 1.321 + (domonad 1.322 + [n1 (m-result (dec n)) 1.323 + n2 (m-result (dec n1)) 1.324 + f1 (fib-trace n1) 1.325 + _ (write [n1 f1]) 1.326 + f2 (fib-trace n2) 1.327 + _ (write [n2 f2]) 1.328 + ] 1.329 + (+ f1 f2)))) 1.330 + 1.331 +) 1.332 + 1.333 +(fib-trace 5) 1.334 + 1.335 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.336 +;; 1.337 +;; Sequences with undefined value: the maybe-t monad transformer 1.338 +;; 1.339 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.340 + 1.341 +; A monad transformer is a function that takes a monad argument and 1.342 +; returns a monad as its result. The resulting monad adds some 1.343 +; specific behaviour aspect to the input monad. 1.344 + 1.345 +; The simplest monad transformer is maybe-t. It adds the functionality 1.346 +; of the maybe monad (handling failures or undefined values) to any other 1.347 +; monad. We illustrate this by applying maybe-t to the sequence monad. 1.348 +; The result is an enhanced sequence monad in which undefined values 1.349 +; (represented by nil) are not subjected to any transformation, but 1.350 +; lead immediately to a nil result in the output. 1.351 + 1.352 +; First we define the combined monad: 1.353 +(def seq-maybe-m (maybe-t sequence-m)) 1.354 + 1.355 +; As a first illustration, we create a range of integers and replace 1.356 +; all even values by nil, using a simple when expression. We use this 1.357 +; sequence in a monad comprehension that yields (inc x). The result 1.358 +; is a sequence in which inc has been applied to all non-nil values, 1.359 +; whereas the nil values appear unmodified in the output: 1.360 +(domonad seq-maybe-m 1.361 + [x (for [n (range 10)] (when (odd? n) n))] 1.362 + (inc x)) 1.363 + 1.364 +; Next we repeat the definition of the function pairs (see above), but 1.365 +; using the seq-maybe monad: 1.366 +(with-monad seq-maybe-m 1.367 + (defn pairs-maybe [xs] 1.368 + (m-seq (list xs xs)))) 1.369 + 1.370 +; Applying this to a sequence containing nils yields the pairs of all 1.371 +; non-nil values interspersed with nils that result from any combination 1.372 +; in which one or both of the values is nil: 1.373 +(pairs-maybe (for [n (range 5)] (when (odd? n) n))) 1.374 + 1.375 +; It is important to realize that undefined values (nil) are not eliminated 1.376 +; from the iterations. They are simply not passed on to any operations. 1.377 +; The outcome of any function applied to arguments of which at least one 1.378 +; is nil is supposed to be nil as well, and the function is never called. 1.379 + 1.380 + 1.381 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.382 +;; 1.383 +;; Continuation-passing style in the cont monad 1.384 +;; 1.385 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 1.386 + 1.387 +; A simple computation performed in continuation-passing style. 1.388 +; (m-result 1) returns a function that, when called with a single 1.389 +; argument f, calls (f 1). The result of the domonad-computation is 1.390 +; a function that behaves in the same way, passing 3 to its function 1.391 +; argument. run-cont executes a continuation by calling it on identity. 1.392 +(run-cont 1.393 + (domonad cont-m 1.394 + [x (m-result 1) 1.395 + y (m-result 2)] 1.396 + (+ x y))) 1.397 + 1.398 +; Let's capture a continuation using call-cc. We store it in a global 1.399 +; variable so that we can do with it whatever we want. The computation 1.400 +; is the same one as in the first example, but it has the side effect 1.401 +; of storing the continuation at (m-result 2). 1.402 +(def continuation nil) 1.403 + 1.404 +(run-cont 1.405 + (domonad cont-m 1.406 + [x (m-result 1) 1.407 + y (call-cc (fn [c] (def continuation c) (c 2)))] 1.408 + (+ x y))) 1.409 + 1.410 +; Now we can call the continuation with whatever argument we want. The 1.411 +; supplied argument takes the place of 2 in the above computation: 1.412 +(run-cont (continuation 5)) 1.413 +(run-cont (continuation 42)) 1.414 +(run-cont (continuation -1)) 1.415 + 1.416 +; Next, a function that illustrates how a captured continuation can be 1.417 +; used as an "emergency exit" out of a computation: 1.418 +(defn sqrt-as-str [x] 1.419 + (call-cc 1.420 + (fn [k] 1.421 + (domonad cont-m 1.422 + [_ (m-when (< x 0) (k (str "negative argument " x)))] 1.423 + (str (. Math sqrt x)))))) 1.424 + 1.425 +(run-cont (sqrt-as-str 2)) 1.426 +(run-cont (sqrt-as-str -2)) 1.427 + 1.428 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;