Mercurial > lasercutter
view src/clojure/contrib/dataflow.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 ;; Copyright (c) Jeffrey Straszheim. All rights reserved. The use and2 ;; distribution terms for this software are covered by the Eclipse Public3 ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can4 ;; be found in the file epl-v10.html at the root of this distribution. By5 ;; using this software in any fashion, you are agreeing to be bound by the6 ;; terms of this license. You must not remove this notice, or any other,7 ;; from this software.8 ;;9 ;; dataflow.clj10 ;;11 ;; A Library to Support a Dataflow Model of State12 ;;13 ;; straszheimjeffrey (gmail)14 ;; Created 10 March 200917 (ns18 ^{:author "Jeffrey Straszheim",19 :doc "A library to support a dataflow model of state"}20 clojure.contrib.dataflow21 (:use [clojure.set :only (union intersection difference)])22 (:use [clojure.contrib.graph :only (directed-graph23 reverse-graph24 dependency-list25 get-neighbors)])26 (:use [clojure.walk :only (postwalk)])27 (:use [clojure.contrib.except :only (throwf)])28 (:import java.io.Writer))31 ;;; Chief Data Structures34 ;; Source Cell36 ; The data of a source cell is directly set by a calling function. It37 ; never depends on other cells.39 (defstruct source-cell40 :name ; The name, a symbol41 :value ; Its value, a Ref42 :cell-type) ; Should be ::source-cell44 ;; Cell46 ; A standard cell that computes its value from other cells.48 (defstruct standard-cell49 :name ; The name, a symbol50 :value ; Its value, a Ref51 :dependents ; The names of cells on which this depends, a collection52 :fun ; A closure that computes the value, given an environment53 :display ; The original expression for display54 :cell-type) ; Should be ::cell56 (derive ::cell ::dependent-cell) ; A cell that has a dependents field58 ;; Validator60 ; A cell that has no value, but can throw an exception when run62 (defstruct validator-cell63 :name ; Always ::validator64 :dependents ; The names of cells on which this depends, a collection65 :fun ; A clojure that can throw an exception66 :display ; The original exprssion for display67 :cell-type) ; Should be ::validator-cell69 (derive ::validator-cell ::dependent-cell)72 ;; A sentinal value74 (def *empty-value* (java.lang.Object.))77 ;; Dataflow79 ; A collection of cells and dependency information81 (defstruct dataflow82 :cells ; A set of all cells83 :cells-map ; A map of cell names (symbols) to collections of cells84 :fore-graph ; The inverse of the dependency graph, nodes are cells85 :topological) ; A vector of sets of independent nodes -- orders the computation88 ;;; Environment Access90 (defn get-cells91 "Get all the cells named by name"92 [df name]93 ((:cells-map @df) name))95 (defn get-cell96 "Get the single cell named by name"97 [df name]98 (let [cells (get-cells df name)]99 (cond100 (= (count cells) 1) (first cells)101 (> (count cells) 1) (throwf Exception "Cell %s has multiple instances" name)102 :otherwise (throwf Exception "Cell %s is undefined" name))))104 (defn source-cell?105 "Is this cell a source cell?"106 [cell]107 (isa? (:cell-type cell) ::source-cell))109 (defn get-source-cells110 "Returns a collection of source cells from the dataflow"111 [df]112 (for [cell (:cells @df)113 :when (source-cell? cell)]114 cell))116 (defn get-value117 "Gets a value from the df matching the passed symbol.118 Signals an error if the name is not present, or if it not a single119 value."120 [df name]121 (let [cell (get-cell df name)122 result @(:value cell)]123 (do (when (= *empty-value* result)124 (throwf Exception "Cell named %s empty" name))125 result)))127 (defn get-values128 "Gets a collection of values from the df by name"129 [df name]130 (let [cells (get-cells df name)131 results (map #(-> % :value deref) cells)]132 (do133 (when (some #(= % *empty-value*) results)134 (throwf Exception "At least one empty cell named %s found" name))135 results)))137 (defn get-old-value138 "Looks up an old value"139 [df env name]140 (if (contains? env name)141 (env name)142 (get-value df name)))144 (defn get-value-from-cell145 "Given a cell, get its value"146 [cell]147 (-> cell :value deref))149 ;;; Build Dataflow Structure151 (defn- build-cells-map152 "Given a collection of cells, build a name->cells-collection map153 from it."154 [cs]155 (let [step (fn [m c]156 (let [n (:name c)157 o (get m n #{})158 s (conj o c)]159 (assoc m n s)))]160 (reduce step {} cs)))162 (defn- build-back-graph163 "Builds the backward dependency graph from the cells map. Each164 node of the graph is a cell."165 [cells cells-map]166 (let [step (fn [n]167 (apply union (for [dep-name (:dependents n)]168 (cells-map dep-name))))169 neighbors (zipmap cells (map step cells))]170 (struct-map directed-graph171 :nodes cells172 :neighbors neighbors)))174 (defn- build-dataflow*175 "Builds the dataflow structure"176 [cs]177 (let [cells (set cs)178 cells-map (build-cells-map cs)179 back-graph (build-back-graph cells cells-map)180 fore-graph (reverse-graph back-graph)]181 (struct-map dataflow182 :cells cells183 :cells-map cells-map184 :fore-graph fore-graph185 :topological (dependency-list back-graph))))187 (def initialize)189 (defn build-dataflow190 "Given a collection of cells, build and return a dataflow object"191 [cs]192 (dosync193 (let [df (ref (build-dataflow* cs))]194 (initialize df)195 df)))198 ;;; Displaying a dataflow200 (defn print-dataflow201 "Prints a dataflow, one cell per line"202 [df]203 (println)204 (let [f (fn [cell] (-> cell :name str))]205 (doseq [cell (sort-by f (:cells @df))]206 (prn cell))))209 ;;; Modifying a Dataflow211 (defn add-cells212 "Given a collection of cells, add them to the dataflow."213 [df cells]214 (dosync215 (let [new-cells (union (set cells) (:cells @df))]216 (ref-set df (build-dataflow* new-cells))217 (initialize df))))219 (defn remove-cells220 "Given a collection of cells, remove them from the dataflow."221 [df cells]222 (dosync223 (let [new-cells (difference (:cells @df) (set cells))]224 (ref-set df (build-dataflow* new-cells))225 (initialize df))))228 ;;; Cell building230 (def *meta* {:type ::dataflow-cell})232 (defn build-source-cell233 "Builds a source cell"234 [name init]235 (with-meta (struct source-cell name (ref init) ::source-cell)236 *meta*))238 (defn- is-col-var?239 [symb]240 (let [name (name symb)]241 (and (= \? (first name))242 (= \* (second name)))))244 (defn- is-old-var?245 [symb]246 (let [name (name symb)]247 (and (= \? (first name))248 (= \- (second name)))))250 (defn- is-var?251 [symb]252 (let [name (name symb)]253 (and (= \? (first name))254 (-> symb is-col-var? not)255 (-> symb is-old-var? not))))257 (defn- cell-name258 [symb]259 `(quote ~(cond (is-var? symb) (-> symb name (.substring 1) symbol)260 (or (is-col-var? symb)261 (is-old-var? symb)) (-> symb name (.substring 2) symbol))))263 (defn- replace-symbol264 "Walk the from replacing the ?X forms with the needed calls"265 [dfs ov form]266 (cond267 (-> form symbol? not) form268 (is-var? form) `(get-value ~dfs ~(cell-name form))269 (is-col-var? form) `(get-values ~dfs ~(cell-name form))270 (is-old-var? form) `(get-old-value ~dfs ~ov ~(cell-name form))271 :otherwise form))273 (defn- build-fun274 "Build the closure needed to compute a cell"275 [form]276 (let [dfs (gensym "df_")277 ov (gensym "old_")]278 `(fn [~dfs ~ov] ~(postwalk (partial replace-symbol dfs ov) form))))280 (defn- get-deps281 "Get the names of the dependent cells"282 [form]283 (let [step (fn [f]284 (cond285 (coll? f) (apply union f)286 (-> f symbol? not) nil287 (is-var? f) #{(cell-name f)}288 (is-col-var? f) #{(cell-name f)}289 (is-old-var? f) #{(cell-name f)}290 :otherwise nil))]291 (postwalk step form)))293 (defn build-standard-cell294 "Builds a standard cell"295 [name deps fun expr]296 (with-meta (struct standard-cell name (ref *empty-value*) deps fun expr ::cell)297 *meta*))299 (defn build-validator-cell300 "Builds a validator cell"301 [deps fun expr]302 (with-meta (struct validator-cell ::validator deps fun expr ::validator-cell)303 *meta*))305 (defmacro cell306 "Build a standard cell, like this:308 (cell fred309 (* ?mary ?joe))311 Which creates a cell named fred that is the product of a cell mary and cell joe313 Or:315 (cell joe316 (apply * ?*sally))318 Which creates a cell that applies * to the collection of all cells named sally320 Or:322 (cell :source fred 0)324 Which builds a source cell fred with initial value 0326 Or:328 (cell :validator (when (< ?fred ?sally)329 (throwf \"%s must be greater than %s\" ?fred ?sally))331 Which will perform the validation"332 [type & data]333 (cond334 (symbol? type) (let [name type ; No type for standard cell335 expr (first data) ; we ignore extra data!336 deps (get-deps expr)337 fun (build-fun expr)]338 `(build-standard-cell '~name ~deps ~fun '~expr))339 (= type :source) (let [[name init] data]340 `(build-source-cell '~name ~init))341 (= type :validator) (let [[expr] data342 deps (get-deps expr)343 fun (build-fun expr)]344 `(build-validator-cell ~deps ~fun '~expr))))347 ;;; Cell Display349 (defmulti display-cell350 "A 'readable' form of the cell"351 :cell-type)353 (defmethod display-cell ::source-cell354 [cell]355 (list 'cell :source (:name cell) (-> cell :value deref)))357 (defmethod display-cell ::cell358 [cell]359 (list 'cell (:name cell) (:display cell) (-> cell :value deref)))361 (defmethod display-cell ::validator-cell362 [cell]363 (list 'cell :validator (:display cell)))365 (defmethod print-method ::dataflow-cell366 [f ^Writer w]367 (binding [*out* w]368 (pr (display-cell f))))371 ;;; Evaluation373 (defmulti eval-cell374 "Evaluate a dataflow cell. Return [changed, old val]"375 (fn [df data old cell] (:cell-type cell)))377 (defmethod eval-cell ::source-cell378 [df data old cell]379 (let [name (:name cell)380 val (:value cell)381 ov @val]382 (if (contains? data name)383 (let [new-val (data name)]384 (if (not= ov new-val)385 (do (ref-set val new-val)386 [true ov])387 [false ov]))388 [false ov])))390 (defmethod eval-cell ::cell391 [df data old cell]392 (let [val (:value cell)393 old-val @val394 new-val ((:fun cell) df old)]395 (if (not= old-val new-val)396 (do (ref-set val new-val)397 [true old-val])398 [false old-val])))400 (defmethod eval-cell ::validator-cell401 [df data old cell]402 (do ((:fun cell) df old)403 [false nil]))405 (defn- perform-flow406 "Evaluate the needed cells (a set) from the given dataflow. Data is407 a name-value mapping of new values for the source cells"408 [df data needed]409 (loop [needed needed410 tops (:topological @df)411 old {}]412 (let [now (first tops) ; Now is a set of nodes413 new-tops (next tops)]414 (when (and (-> needed empty? not)415 (-> now empty? not))416 (let [step (fn [[needed old] cell]417 (let [[changed ov] (try418 (eval-cell df data old cell)419 (catch Exception e420 (throw (Exception. (str cell) e))))421 nn (disj needed cell)]422 (if changed423 [(union nn (get-neighbors (:fore-graph @df) cell))424 (assoc old (:name cell) ov)]425 [nn old])))426 [new-needed new-old] (reduce step427 [needed old]428 (intersection now needed))]429 (recur new-needed new-tops new-old))))))431 (defn- validate-update432 "Ensure that all the updated cells are source cells"433 [df names]434 (let [scns (set (map :name (get-source-cells df)))]435 (doseq [name names]436 (when (-> name scns not)437 (throwf Exception "Cell %n is not a source cell" name)))))439 (defn update-values440 "Given a dataflow, and a map of name-value pairs, update the441 dataflow by binding the new values. Each name must be of a source442 cell"443 [df data]444 (dosync445 (validate-update df (keys data))446 (let [needed (apply union (for [name (keys data)]447 (set ((:cells-map @df) name))))]448 (perform-flow df data needed))))450 (defn- initialize451 "Apply all the current source cell values. Useful for a new452 dataflow, or one that has been updated with new cells"453 [df]454 (let [needed (:cells @df)455 fg (:fore-graph @df)]456 (perform-flow df {} needed)))459 ;;; Watchers461 (defn add-cell-watcher462 "Adds a watcher to a cell to respond to changes of value. The is a463 function of 4 values: a key, the cell, its old value, its new464 value. This is implemented using Clojure's add-watch to the465 underlying ref, and shared its sematics"466 [cell key fun]467 (let [val (:value cell)]468 (add-watch val key (fn [key _ old-v new-v]469 (fun key cell old-v new-v)))))472 (comment474 (def df475 (build-dataflow476 [(cell :source fred 1)477 (cell :source mary 0)478 (cell greg (+ ?fred ?mary))479 (cell joan (+ ?fred ?mary))480 (cell joan (* ?fred ?mary))481 (cell sally (apply + ?*joan))482 (cell :validator (when (number? ?-greg)483 (when (<= ?greg ?-greg)484 (throwf Exception "Non monotonic"))))]))486 (do (println)487 (print-dataflow df))489 (add-cell-watcher (get-cell df 'sally)490 nil491 (fn [key cell o n]492 (printf "sally changed from %s to %s\n" o n)))494 (update-values df {'fred 1 'mary 1})495 (update-values df {'fred 5 'mary 1})496 (update-values df {'fred 0 'mary 0})498 (get-value df 'fred)499 (get-values df 'joan)500 (get-value df 'sally)501 (get-value df 'greg)503 (use :reload 'clojure.contrib.dataflow)504 (use 'clojure.stacktrace) (e)505 (use 'clojure.contrib.trace)506 )509 ;; End of file