Mercurial > lasercutter
view src/clojure/contrib/monads.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 ;; Monads in Clojure3 ;; by Konrad Hinsen4 ;; last updated June 30, 20096 ;; Copyright (c) Konrad Hinsen, 2009. All rights reserved. The use7 ;; and distribution terms for this software are covered by the Eclipse8 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)9 ;; which can be found in the file epl-v10.html at the root of this10 ;; distribution. By using this software in any fashion, you are11 ;; agreeing to be bound by the terms of this license. You must not12 ;; remove this notice, or any other, from this software.14 (ns15 ^{:author "Konrad Hinsen"16 :see-also [["http://onclojure.com/2009/03/05/a-monad-tutorial-for-clojure-programmers-part-1/" "Monad tutorial part 1"]17 ["http://onclojure.com/2009/03/06/a-monad-tutorial-for-clojure-programmers-part-2/" "Monad tutorial part 2"]18 ["http://onclojure.com/2009/03/23/a-monad-tutorial-for-clojure-programmers-part-3/" "Monad tutorial part 3"]19 ["http://onclojure.com/2009/04/24/a-monad-tutorial-for-clojure-programmers-part-4/" "Monad tutorial part 4"]20 ["http://intensivesystems.net/tutorials/monads_101.html" "Monads in Clojure part 1"]21 ["http://intensivesystems.net/tutorials/monads_201.html" "Monads in Clojure part 2"]]22 :doc "This library contains the most commonly used monads as well23 as macros for defining and using monads and useful monadic24 functions."}25 clojure.contrib.monads26 (:require [clojure.contrib.accumulators])27 (:use [clojure.contrib.macro-utils :only (with-symbol-macros defsymbolmacro)])28 (:use [clojure.contrib.def :only (name-with-attributes)]))30 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;31 ;;32 ;; Defining monads33 ;;34 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;36 (defmacro monad37 "Define a monad by defining the monad operations. The definitions38 are written like bindings to the monad operations m-bind and39 m-result (required) and m-zero and m-plus (optional)."40 [operations]41 `(let [~'m-bind ::undefined42 ~'m-result ::undefined43 ~'m-zero ::undefined44 ~'m-plus ::undefined45 ~@operations]46 {:m-result ~'m-result47 :m-bind ~'m-bind48 :m-zero ~'m-zero49 :m-plus ~'m-plus}))51 (defmacro defmonad52 "Define a named monad by defining the monad operations. The definitions53 are written like bindings to the monad operations m-bind and54 m-result (required) and m-zero and m-plus (optional)."56 ([name doc-string operations]57 (let [doc-name (with-meta name {:doc doc-string})]58 `(defmonad ~doc-name ~operations)))60 ([name operations]61 `(def ~name (monad ~operations))))64 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;65 ;;66 ;; Using monads67 ;;68 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;70 (defn- add-monad-step71 "Add a monad comprehension step before the already transformed72 monad comprehension expression mexpr."73 [mexpr step]74 (let [[bform expr] step]75 (cond (identical? bform :when) `(if ~expr ~mexpr ~'m-zero)76 (identical? bform :let) `(let ~expr ~mexpr)77 :else (list 'm-bind expr (list 'fn [bform] mexpr)))))79 (defn- monad-expr80 "Transforms a monad comprehension, consisting of a list of steps81 and an expression defining the final value, into an expression82 chaining together the steps using :bind and returning the final value83 using :result. The steps are given as a vector of84 binding-variable/monadic-expression pairs."85 [steps expr]86 (when (odd? (count steps))87 (throw (Exception. "Odd number of elements in monad comprehension steps")))88 (let [rsteps (reverse (partition 2 steps))89 [lr ls] (first rsteps)]90 (if (= lr expr)91 ; Optimization: if the result expression is equal to the result92 ; of the last computation step, we can eliminate an m-bind to93 ; m-result.94 (reduce add-monad-step95 ls96 (rest rsteps))97 ; The general case.98 (reduce add-monad-step99 (list 'm-result expr)100 rsteps))))102 (defmacro with-monad103 "Evaluates an expression after replacing the keywords defining the104 monad operations by the functions associated with these keywords105 in the monad definition given by name."106 [monad & exprs]107 `(let [name# ~monad108 ~'m-bind (:m-bind name#)109 ~'m-result (:m-result name#)110 ~'m-zero (:m-zero name#)111 ~'m-plus (:m-plus name#)]112 (with-symbol-macros ~@exprs)))114 (defmacro domonad115 "Monad comprehension. Takes the name of a monad, a vector of steps116 given as binding-form/monadic-expression pairs, and a result value117 specified by expr. The monadic-expression terms can use the binding118 variables of the previous steps.119 If the monad contains a definition of m-zero, the step list can also120 contain conditions of the form :when p, where the predicate p can121 contain the binding variables from all previous steps.122 A clause of the form :let [binding-form expr ...], where the bindings123 are given as a vector as for the use in let, establishes additional124 bindings that can be used in the following steps."125 ([steps expr]126 (monad-expr steps expr))127 ([name steps expr]128 (let [mexpr (monad-expr steps expr)]129 `(with-monad ~name ~mexpr))))131 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;132 ;;133 ;; Defining functions used with monads134 ;;135 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;137 (defmacro defmonadfn138 "Like defn, but for functions that use monad operations and are used inside139 a with-monad block."140 {:arglists '([name docstring? attr-map? args expr]141 [name docstring? attr-map? (args expr) ...])}142 [name & options]143 (let [[name options] (name-with-attributes name options)144 fn-name (symbol (str *ns*) (format "m+%s+m" (str name)))145 make-fn-body (fn [args expr]146 (list (vec (concat ['m-bind 'm-result147 'm-zero 'm-plus] args))148 (list `with-symbol-macros expr)))]149 (if (list? (first options))150 ; multiple arities151 (let [arglists (map first options)152 exprs (map second options)153 ]154 `(do155 (defsymbolmacro ~name (partial ~fn-name ~'m-bind ~'m-result156 ~'m-zero ~'m-plus))157 (defn ~fn-name ~@(map make-fn-body arglists exprs))))158 ; single arity159 (let [[args expr] options]160 `(do161 (defsymbolmacro ~name (partial ~fn-name ~'m-bind ~'m-result162 ~'m-zero ~'m-plus))163 (defn ~fn-name ~@(make-fn-body args expr)))))))166 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;167 ;;168 ;; Commonly used monad functions169 ;;170 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;172 ; Define the four basic monad operations as symbol macros that173 ; expand to their unqualified symbol equivalents. This makes it possible174 ; to use them inside macro templates without having to quote them.175 (defsymbolmacro m-result m-result)176 (defsymbolmacro m-bind m-bind)177 (defsymbolmacro m-zero m-zero)178 (defsymbolmacro m-plus m-plus)180 (defmacro m-lift181 "Converts a function f of n arguments into a function of n182 monadic arguments returning a monadic value."183 [n f]184 (let [expr (take n (repeatedly #(gensym "x_")))185 vars (vec (take n (repeatedly #(gensym "mv_"))))186 steps (vec (interleave expr vars))]187 (list `fn vars (monad-expr steps (cons f expr)))))189 (defmonadfn m-join190 "Converts a monadic value containing a monadic value into a 'simple'191 monadic value."192 [m]193 (m-bind m identity))195 (defmonadfn m-fmap196 "Bind the monadic value m to the function returning (f x) for argument x"197 [f m]198 (m-bind m (fn [x] (m-result (f x)))))200 (defmonadfn m-seq201 "'Executes' the monadic values in ms and returns a sequence of the202 basic values contained in them."203 [ms]204 (reduce (fn [q p]205 (m-bind p (fn [x]206 (m-bind q (fn [y]207 (m-result (cons x y)))) )))208 (m-result '())209 (reverse ms)))211 (defmonadfn m-map212 "'Executes' the sequence of monadic values resulting from mapping213 f onto the values xs. f must return a monadic value."214 [f xs]215 (m-seq (map f xs)))217 (defmonadfn m-chain218 "Chains together monadic computation steps that are each functions219 of one parameter. Each step is called with the result of the previous220 step as its argument. (m-chain (step1 step2)) is equivalent to221 (fn [x] (domonad [r1 (step1 x) r2 (step2 r1)] r2))."222 [steps]223 (reduce (fn m-chain-link [chain-expr step]224 (fn [v] (m-bind (chain-expr v) step)))225 m-result226 steps))228 (defmonadfn m-reduce229 "Return the reduction of (m-lift 2 f) over the list of monadic values mvs230 with initial value (m-result val)."231 ([f mvs]232 (if (empty? mvs)233 (m-result (f))234 (let [m-f (m-lift 2 f)]235 (reduce m-f mvs))))236 ([f val mvs]237 (let [m-f (m-lift 2 f)238 m-val (m-result val)]239 (reduce m-f m-val mvs))))241 (defmonadfn m-until242 "While (p x) is false, replace x by the value returned by the243 monadic computation (f x). Return (m-result x) for the first244 x for which (p x) is true."245 [p f x]246 (if (p x)247 (m-result x)248 (domonad249 [y (f x)250 z (m-until p f y)]251 z)))253 (defmacro m-when254 "If test is logical true, return monadic value m-expr, else return255 (m-result nil)."256 [test m-expr]257 `(if ~test ~m-expr (~'m-result nil)))259 (defmacro m-when-not260 "If test if logical false, return monadic value m-expr, else return261 (m-result nil)."262 [test m-expr]263 `(if ~test (~'m-result nil) ~m-expr))265 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;266 ;;267 ;; Utility functions used in monad definitions268 ;;269 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;271 (defn- flatten*272 "Like #(apply concat %), but fully lazy: it evaluates each sublist273 only when it is needed."274 [ss]275 (lazy-seq276 (when-let [s (seq ss)]277 (concat (first s) (flatten* (rest s))))))279 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;280 ;;281 ;; Commonly used monads282 ;;283 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;285 ; Identity monad286 (defmonad identity-m287 "Monad describing plain computations. This monad does in fact nothing288 at all. It is useful for testing, for combination with monad289 transformers, and for code that is parameterized with a monad."290 [m-result identity291 m-bind (fn m-result-id [mv f]292 (f mv))293 ])295 ; Maybe monad296 (defmonad maybe-m297 "Monad describing computations with possible failures. Failure is298 represented by nil, any other value is considered valid. As soon as299 a step returns nil, the whole computation will yield nil as well."300 [m-zero nil301 m-result (fn m-result-maybe [v] v)302 m-bind (fn m-bind-maybe [mv f]303 (if (nil? mv) nil (f mv)))304 m-plus (fn m-plus-maybe [& mvs]305 (first (drop-while nil? mvs)))306 ])308 ; Sequence monad (called "list monad" in Haskell)309 (defmonad sequence-m310 "Monad describing multi-valued computations, i.e. computations311 that can yield multiple values. Any object implementing the seq312 protocol can be used as a monadic value."313 [m-result (fn m-result-sequence [v]314 (list v))315 m-bind (fn m-bind-sequence [mv f]316 (flatten* (map f mv)))317 m-zero (list)318 m-plus (fn m-plus-sequence [& mvs]319 (flatten* mvs))320 ])322 ; Set monad323 (defmonad set-m324 "Monad describing multi-valued computations, like sequence-m,325 but returning sets of results instead of sequences of results."326 [m-result (fn m-result-set [v]327 #{v})328 m-bind (fn m-bind-set [mv f]329 (apply clojure.set/union (map f mv)))330 m-zero #{}331 m-plus (fn m-plus-set [& mvs]332 (apply clojure.set/union mvs))333 ])335 ; State monad336 (defmonad state-m337 "Monad describing stateful computations. The monadic values have the338 structure (fn [old-state] [result new-state])."339 [m-result (fn m-result-state [v]340 (fn [s] [v s]))341 m-bind (fn m-bind-state [mv f]342 (fn [s]343 (let [[v ss] (mv s)]344 ((f v) ss))))345 ])347 (defn update-state348 "Return a state-monad function that replaces the current state by the349 result of f applied to the current state and that returns the old state."350 [f]351 (fn [s] [s (f s)]))353 (defn set-state354 "Return a state-monad function that replaces the current state by s and355 returns the previous state."356 [s]357 (update-state (fn [_] s)))359 (defn fetch-state360 "Return a state-monad function that returns the current state and does not361 modify it."362 []363 (update-state identity))365 (defn fetch-val366 "Return a state-monad function that assumes the state to be a map and367 returns the value corresponding to the given key. The state is not modified."368 [key]369 (domonad state-m370 [s (fetch-state)]371 (key s)))373 (defn update-val374 "Return a state-monad function that assumes the state to be a map and375 replaces the value associated with the given key by the return value376 of f applied to the old value. The old value is returned."377 [key f]378 (fn [s]379 (let [old-val (get s key)380 new-s (assoc s key (f old-val))]381 [old-val new-s])))383 (defn set-val384 "Return a state-monad function that assumes the state to be a map and385 replaces the value associated with key by val. The old value is returned."386 [key val]387 (update-val key (fn [_] val)))389 (defn with-state-field390 "Returns a state-monad function that expects a map as its state and391 runs statement (another state-monad function) on the state defined by392 the map entry corresponding to key. The map entry is updated with the393 new state returned by statement."394 [key statement]395 (fn [s]396 (let [substate (get s key nil)397 [result new-substate] (statement substate)398 new-state (assoc s key new-substate)]399 [result new-state])))401 (defn state-m-until402 "An optimized implementation of m-until for the state monad that403 replaces recursion by a loop."404 [p f x]405 (letfn [(until [p f x s]406 (if (p x)407 [x s]408 (let [[x s] ((f x) s)]409 (recur p f x s))))]410 (fn [s] (until p f x s))))412 ; Writer monad413 (defn writer-m414 "Monad describing computations that accumulate data on the side, e.g. for415 logging. The monadic values have the structure [value log]. Any of the416 accumulators from clojure.contrib.accumulators can be used for storing the417 log data. Its empty value is passed as a parameter."418 [empty-accumulator]419 (monad420 [m-result (fn m-result-writer [v]421 [v empty-accumulator])422 m-bind (fn m-bind-writer [mv f]423 (let [[v1 a1] mv424 [v2 a2] (f v1)]425 [v2 (clojure.contrib.accumulators/combine a1 a2)]))426 ]))428 (defmonadfn write [v]429 (let [[_ a] (m-result nil)]430 [nil (clojure.contrib.accumulators/add a v)]))432 (defn listen [mv]433 (let [[v a] mv] [[v a] a]))435 (defn censor [f mv]436 (let [[v a] mv] [v (f a)]))438 ; Continuation monad440 (defmonad cont-m441 "Monad describing computations in continuation-passing style. The monadic442 values are functions that are called with a single argument representing443 the continuation of the computation, to which they pass their result."444 [m-result (fn m-result-cont [v]445 (fn [c] (c v)))446 m-bind (fn m-bind-cont [mv f]447 (fn [c]448 (mv (fn [v] ((f v) c)))))449 ])451 (defn run-cont452 "Execute the computation c in the cont monad and return its result."453 [c]454 (c identity))456 (defn call-cc457 "A computation in the cont monad that calls function f with a single458 argument representing the current continuation. The function f should459 return a continuation (which becomes the return value of call-cc),460 or call the passed-in current continuation to terminate."461 [f]462 (fn [c]463 (let [cc (fn cc [a] (fn [_] (c a)))464 rc (f cc)]465 (rc c))))468 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;469 ;;470 ;; Monad transformers471 ;;472 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;474 (defmacro monad-transformer475 "Define a monad transforer in terms of the monad operations and the base476 monad. The argument which-m-plus chooses if m-zero and m-plus are taken477 from the base monad or from the transformer."478 [base which-m-plus operations]479 `(let [which-m-plus# (cond (= ~which-m-plus :m-plus-default)480 (if (= ::undefined (with-monad ~base ~'m-plus))481 :m-plus-from-transformer482 :m-plus-from-base)483 (or (= ~which-m-plus :m-plus-from-base)484 (= ~which-m-plus :m-plus-from-transformer))485 ~which-m-plus486 :else487 (throw (java.lang.IllegalArgumentException.488 "undefined m-plus choice")))489 combined-monad# (monad ~operations)]490 (if (= which-m-plus# :m-plus-from-base)491 (assoc combined-monad#492 :m-zero (with-monad ~base ~'m-zero)493 :m-plus (with-monad ~base ~'m-plus))494 combined-monad#)))496 (defn maybe-t497 "Monad transformer that transforms a monad m into a monad in which498 the base values can be invalid (represented by nothing, which defaults499 to nil). The third argument chooses if m-zero and m-plus are inherited500 from the base monad (use :m-plus-from-base) or adopt maybe-like501 behaviour (use :m-plus-from-transformer). The default is :m-plus-from-base502 if the base monad m has a definition for m-plus, and503 :m-plus-from-transformer otherwise."504 ([m] (maybe-t m nil :m-plus-default))505 ([m nothing] (maybe-t m nothing :m-plus-default))506 ([m nothing which-m-plus]507 (monad-transformer m which-m-plus508 [m-result (with-monad m m-result)509 m-bind (with-monad m510 (fn m-bind-maybe-t [mv f]511 (m-bind mv512 (fn [x]513 (if (identical? x nothing)514 (m-result nothing)515 (f x))))))516 m-zero (with-monad m (m-result nothing))517 m-plus (with-monad m518 (fn m-plus-maybe-t [& mvs]519 (if (empty? mvs)520 (m-result nothing)521 (m-bind (first mvs)522 (fn [v]523 (if (= v nothing)524 (apply m-plus-maybe-t (rest mvs))525 (m-result v)))))))526 ])))528 (defn sequence-t529 "Monad transformer that transforms a monad m into a monad in which530 the base values are sequences. The argument which-m-plus chooses531 if m-zero and m-plus are inherited from the base monad532 (use :m-plus-from-base) or adopt sequence-like533 behaviour (use :m-plus-from-transformer). The default is :m-plus-from-base534 if the base monad m has a definition for m-plus, and535 :m-plus-from-transformer otherwise."536 ([m] (sequence-t m :m-plus-default))537 ([m which-m-plus]538 (monad-transformer m which-m-plus539 [m-result (with-monad m540 (fn m-result-sequence-t [v]541 (m-result (list v))))542 m-bind (with-monad m543 (fn m-bind-sequence-t [mv f]544 (m-bind mv545 (fn [xs]546 (m-fmap flatten*547 (m-map f xs))))))548 m-zero (with-monad m (m-result (list)))549 m-plus (with-monad m550 (fn m-plus-sequence-t [& mvs]551 (m-reduce concat (list) mvs)))552 ])))554 ;; Contributed by Jim Duey555 (defn state-t556 "Monad transformer that transforms a monad m into a monad of stateful557 computations that have the base monad type as their result."558 [m]559 (monad [m-result (with-monad m560 (fn m-result-state-t [v]561 (fn [s]562 (m-result [v s]))))563 m-bind (with-monad m564 (fn m-bind-state-t [stm f]565 (fn [s]566 (m-bind (stm s)567 (fn [[v ss]]568 ((f v) ss))))))569 m-zero (with-monad m570 (if (= ::undefined m-zero)571 ::undefined572 (fn [s]573 m-zero)))574 m-plus (with-monad m575 (if (= ::undefined m-plus)576 ::undefined577 (fn [& stms]578 (fn [s]579 (apply m-plus (map #(% s) stms))))))580 ]))