Mercurial > lasercutter
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 examples5 ;;6 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9 (ns10 #^{:author "Konrad Hinsen"11 :skip-wiki true12 :doc "Examples for using monads"}13 clojure.contrib.monads.examples14 (:use [clojure.contrib.monads15 :only (domonad with-monad m-lift m-seq m-reduce m-when16 sequence-m17 maybe-m18 state-m fetch-state set-state19 writer-m write20 cont-m run-cont call-cc21 maybe-t)])22 (:require (clojure.contrib [accumulators :as accu])))24 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;25 ;;26 ;; Sequence manipulations with the sequence monad27 ;;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 sequences33 ; created by sequence monad operations are lazy.35 ; Monad comprehensions in the sequence monad work exactly the same36 ; as Clojure's 'for' construct, except that :while clauses are not37 ; available.38 (domonad sequence-m39 [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-m45 (domonad46 [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-m52 [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-m59 [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-m68 (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 takes74 ; a sequence of monadic values and returns a monadic value containing75 ; the sequence of the underlying values, obtained from chaining together76 ; from left to right the monadic values in the sequence.77 (with-monad sequence-m78 (defn pairs [xs]79 (m-seq (list xs xs))))81 (pairs (range 5))83 ; This definition suggests a generalization:84 (with-monad sequence-m85 (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-m93 [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-m99 [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 monad107 ;;108 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;110 ; Maybe monad versions of basic arithmetic111 (with-monad maybe-m112 (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's117 ; not a legal Clojure symbol, and we want it to fail if a division by zero118 ; is attempted. It is best defined by a monad comprehension with a119 ; :when clause:120 (defn safe-div [x y]121 (domonad maybe-m122 [a x123 b y124 :when (not (zero? b))]125 (/ a b)))127 ; Now do some non-trivial computation with division128 ; It fails for (1) x = 0, (2) y = 0 or (3) y = -x.129 (with-monad maybe-m130 (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 that142 ; holds a valid value.143 (with-monad maybe-m144 (m-plus (some-function 2 0) (some-function 2 -2) (some-function 2 3)))146 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;147 ;;148 ;; Random numbers with the state monad149 ;;150 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;152 ; A state monad item represents a computation that changes a state and153 ; returns a value. Its structure is a function that takes a state argument154 ; 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 monad156 ; expression is a state monad item (thus a function), and everything you157 ; get out as well. A state monad does not perform a calculation, it158 ; 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 the162 ; pseudo-random value derived from this state and the updated state163 ; for the next iteration. This is exactly the structure of a state164 ; monad item.165 (defn rng [seed]166 (let [m 259200167 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 seq172 ; of values obtained from iteratively applying a state monad value.173 (defn value-seq [f seed]174 (lazy-seq175 (let [[value next] (f seed)]176 (cons value (value-seq f next)))))178 ; Next, we define basic statistics functions to check our random numbers179 (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.), so187 ; 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 numbers193 ; from rng's distribution, shifted by -6, has a distribution that is194 ; approximately Gaussian with 0 mean and variance 1, by virtue of the central195 ; limit theorem.196 ; In the first version, we call rng 12 times explicitly and calculate the197 ; shifted sum in a monad comprehension:198 (def gaussian1199 (domonad state-m200 [x1 rng201 x2 rng202 x3 rng203 x4 rng204 x5 rng205 x6 rng206 x7 rng207 x8 rng208 x9 rng209 x10 rng210 x11 rng211 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 12219 ; random numbers. This would be easy if we could define a summation220 ; operation on random-number generators, which would then be used in221 ; combination with reduce. The lift operation gives us exactly that.222 ; More precisely, we need (m-lift 2 +), because we want both arguments223 ; of + to be lifted to the state monad:224 (def gaussian2225 (domonad state-m226 [sum12 (reduce (m-lift 2 +) (replicate 12 rng))]227 (- sum12 6.)))229 ; Such a reduction is often quite useful, so there's m-reduce predefined230 ; to simplify it:231 (def gaussian2232 (domonad state-m233 [sum12 (m-reduce + (replicate 12 rng))]234 (- sum12 6.)))236 ; The statistics should be strictly the same as above, as long as237 ; 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 rid242 ; of the monad comprehension altogether:243 (with-monad state-m244 (def gaussian3245 ((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 generator253 ; that yields a list of two random numbers. The m-seq operation can easily254 ; provide it:255 (with-monad state-m256 (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 random262 ; number generator and go back to that saved seed later on:263 (def identical-random-seqs264 (domonad state-m265 [seed (fetch-state)266 x1 rng267 x2 rng268 _ (set-state seed)269 y1 rng270 y2 rng]271 (list [x1 x2] [y1 y2])))273 (identical-random-seqs 1)275 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;276 ;;277 ;; Logging with the writer monad278 ;;279 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;281 ; A basic logging example282 (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 of290 ; a naive implementation of a Fibonacci function. The starting point is:291 (defn fib [n]292 (if (< n 2)293 n294 (let [n1 (dec n)295 n2 (dec n1)]296 (+ (fib n1) (fib n2)))))298 ; First we rewrite it to make every computational step explicit299 ; in a let expression:300 (defn fib [n]301 (if (< n 2)302 n303 (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 a310 ; vector accumulator. We can then place calls to write in between the311 ; steps, and obtain as a result both the return value of the function312 ; 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 (domonad319 [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 transformer335 ;;336 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;338 ; A monad transformer is a function that takes a monad argument and339 ; returns a monad as its result. The resulting monad adds some340 ; specific behaviour aspect to the input monad.342 ; The simplest monad transformer is maybe-t. It adds the functionality343 ; of the maybe monad (handling failures or undefined values) to any other344 ; monad. We illustrate this by applying maybe-t to the sequence monad.345 ; The result is an enhanced sequence monad in which undefined values346 ; (represented by nil) are not subjected to any transformation, but347 ; 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 replace353 ; all even values by nil, using a simple when expression. We use this354 ; sequence in a monad comprehension that yields (inc x). The result355 ; 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-m358 [x (for [n (range 10)] (when (odd? n) n))]359 (inc x))361 ; Next we repeat the definition of the function pairs (see above), but362 ; using the seq-maybe monad:363 (with-monad seq-maybe-m364 (defn pairs-maybe [xs]365 (m-seq (list xs xs))))367 ; Applying this to a sequence containing nils yields the pairs of all368 ; non-nil values interspersed with nils that result from any combination369 ; 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 eliminated373 ; 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 one375 ; is nil is supposed to be nil as well, and the function is never called.378 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;379 ;;380 ;; Continuation-passing style in the cont monad381 ;;382 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;384 ; A simple computation performed in continuation-passing style.385 ; (m-result 1) returns a function that, when called with a single386 ; argument f, calls (f 1). The result of the domonad-computation is387 ; a function that behaves in the same way, passing 3 to its function388 ; argument. run-cont executes a continuation by calling it on identity.389 (run-cont390 (domonad cont-m391 [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 global396 ; variable so that we can do with it whatever we want. The computation397 ; is the same one as in the first example, but it has the side effect398 ; of storing the continuation at (m-result 2).399 (def continuation nil)401 (run-cont402 (domonad cont-m403 [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. The408 ; 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 be414 ; used as an "emergency exit" out of a computation:415 (defn sqrt-as-str [x]416 (call-cc417 (fn [k]418 (domonad cont-m419 [_ (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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;