1 #+TITLE: Concurrent Propagator System
2 #+OPTIONS: num:nil ^:nil
7 :tangle: src/propagator.clj
11 #^{:author "Eric Schulte",
13 :doc "Simple concurrent propagator system."}
15 (:use clojure.contrib.repl-utils clojure.contrib.math))
17 (defmacro cell "Define a new cell."
19 `(def ~name (agent ~state)))
21 ;; record keeping for graphing propagators
23 (defmacro remember-prop [name in-cells out-cells]
27 {:in-cells (map (fn [x#] (name x#)) (quote ~in-cells))
28 :out-cells (map (fn [x#] (name x#)) (quote ~out-cells))})))
30 (defmacro propagator "Define a new propagator."
31 [name in-cells out-cells & body]
33 (remember-prop ~name ~in-cells ~out-cells)
34 (defn ~(with-meta name
36 :in-cells in-cells :out-cells out-cells))
38 (doseq [cell# ~in-cells] (add-neighbor cell# ~name))
41 (defn set-cell "Set the value of a cell" [cell value]
42 (send cell (fn [_] value)))
44 (defmacro run-propagator
45 "Run a propagator, first collect the most recent values from all
46 cells associated with the propagator, then evaluate."
48 `(let [results# (apply ~propagator (map deref (:in-cells ^#'~propagator)))]
49 (doseq [cell# (:out-cells ^#'~propagator)]
50 (when (not (= @cell# results#))
51 (send cell# (fn [_#] results#))))
54 (defmacro add-neighbor "Add a neighbor to the given cell."
58 (agent nil :validator (fn [_#] (do (future (run-propagator ~neighbor)) true)))
62 ** graphs of the propagator network
63 draw a graph in a JFrame
64 #+begin_src clojure :tangle src/graphs.clj
67 (import '(javax.swing JFrame JPanel)
68 '(java.awt Color Graphics)
69 '(java.awt.image BufferedImage))
75 (defn view "Display a graph generated by vijual" [img]
77 width (* (.getWidth img) mult)
78 height (* (.getHeight img) mult)
80 panel (doto (proxy [JPanel] []
82 (.drawImage g img offset offset nil))))]
83 (doto frame (.add panel) .pack (.setSize (+ offset width) (+ offset height)).show)))
85 (defn graph-propagators []
89 (let [hsh (get prop-graph key)]
91 (map #(vec (list % (name key))) (get hsh :in-cells))
92 (map #(vec (list (name key) %)) (get hsh :out-cells)))))
93 (keys prop-graph))))))
96 (view (draw-directed-graph-image (graph-propagators))))
98 (defn clear-network []
100 (def frame (JFrame.)))
104 ** doubling a number -- simplest
108 (propagator doubler [in-c] [out-c] (* 2 in))
109 ;; then any updates to in
111 ;; will propagate to out
115 ** square roots -- heron
122 (propagator enough [x guess] [done]
124 (if (< (abs (- (* guess guess) x)) @margin) true false))
126 (propagator heron [x done guess] [guess]
130 (/ (+ guess (/ x guess)) 2.0)))
134 Alright, this will use Clojure's [[http://github.com/mmcgrana/ring][Ring]] web server middle-ware.
136 So, I guess the best demo here would be some reading/writing through a
139 The =app= will dispatch the incoming data to input cell by the route
140 at the end of the url, then there can be a couple of output cells
141 which will render different views of the related data.
143 #+begin_src clojure :tangle src/web.clj
144 (load-file "/home/eschulte/src/propagator/src/propagator.clj")
146 (use 'ring.adapter.jetty)
147 (use 'clojure.contrib.seq-utils)
148 (import 'java.util.Date 'java.text.SimpleDateFormat)
154 (propagator adder [input names] [names]
155 (when (> (count (seq input)) 0)
156 (set (cons (str input " :1") names))))
160 ;; page to accept input
161 (when (= "/" (:uri req))
163 :headers {"Content-Type" "text/html"
166 "<form name=\"\" action=\"add\" method=\"get\">"
167 "Word: <input type=\"type\" name=\"word\" />"
168 "<input type=\"submit\" value=\"Submit\" />"
170 ;; dump value into "input" cell
171 (when (re-matches #"/add" (:uri req))
172 (set-cell input (second (re-matches #".+=(.+)" (:query-string req))))
173 {:status 303 :headers {"Location" "../list"}})
174 ;; render the value of the "list" cell
175 (when-let [matched (re-matches #"/list" (:uri req))]
177 :headers {"Content-Type" "text/html"}
178 :body (apply str (flatten (list "<ul>"
179 (map #(str "<li>"%"</li>") @names)
181 "<p><a href=\"/\">another word</a></p>")))})))
183 (run-jetty #'app {:port 3000})
187 ** look at mutable data stores
188 - http://clojure.org/agents
189 - http://clojure.org/atoms
190 - http://clojure.org/refs
192 most definitely will use agents, functions to set their values are
193 applied to them with send (or send-off if potentially I/O bound), they
194 support validators, and they can be set to run actions (i.e. alert
195 propagators) as part of their update cycle (with add-watcher).
197 ** design to permit distribution across multiple machines
198 it should be possible to wrap these mutable items including
199 - network connections from other machines
200 - hardware (timers, I/O devices, etc...)
202 in cells, so that the propagator system remains "pure"