1 #+TITLE: Concurrent Propagator in Clojure
2 #+OPTIONS: num:nil ^:nil toc:2
5 A concurrent propagator system implemented in [[http://clojure.org][Clojure]]. This simple
6 project builds on the scheme propagator system from Gerald Sussman's
7 [[http://dspace.mit.edu/handle/1721.1/44215][The art of the Propagator]].
10 We develop a programming model built on the idea that the basic
11 computational elements are autonomous machines interconnected by
12 shared cells through which they communicate. Each machine
13 continuously examines the cells it is interested in, and adds
14 information to some based on deductions it can make from information
15 from the others. This model makes it easy to smoothly combine
16 expression-oriented and constraint-based programming; it also easily
17 accommodates implicit incremental distributed search in ordinary
18 programs. This work builds on the original research of Guy Lewis
19 Steele Jr. and was developed more recently with the help of Chris
23 Major differences between this system and the one implemented in /The
24 art of the Propagator/ are that,
25 1) this implementation admits parallel execution through the use of
26 Clojure's [[http://clojure.org/agents][agents]].
27 2) this version does not implement the backtracking system which
28 breaks the locality of the propagator system
30 Source code and documentation are available as a [[http://github.com/technomancy/leiningen][leiningen]] project,
31 and can be checked out using [[http://git-scm.com/][git]] from [[http://gitweb.adaptive.cs.unm.edu/propagator.git][here]] with
32 : git clone http://gitweb.adaptive.cs.unm.edu/propagator.git
34 (note: this is an exploratory, educational implementation and isn't
35 suitable for any sort of /production/ application.)
39 :tangle: src/propagator.clj
41 tangled to [[file:src/propagator.clj]]
45 #^{:author "Eric Schulte",
47 :doc "Simple concurrent propagator system."}
49 (:use clojure.contrib.repl-utils clojure.contrib.math))
51 (defmacro cell "Define a new cell."
53 `(def ~name (agent ~state)))
55 (defn set-cell "Set the value of a cell" [cell value]
56 (send cell (fn [_] value)))
58 (defmacro propagator "Define a new propagator."
59 [name in-cells out-cells & body]
61 (defn ~(with-meta name
63 :in-cells in-cells :out-cells out-cells))
65 (doseq [cell# ~in-cells] (add-neighbor cell# ~name))
68 (defmacro run-propagator
69 "Run a propagator, first collect the most recent values from all
70 cells associated with the propagator, then evaluate."
72 `(let [results# (apply ~propagator (map deref (:in-cells ^#'~propagator)))]
73 (doseq [cell# (:out-cells ^#'~propagator)]
74 (when (not (= @cell# results#))
75 (send cell# (fn [_#] results#))))
78 (defmacro add-neighbor "Add a neighbor to the given cell."
82 (agent nil :validator (fn [_#] (do (future (run-propagator ~neighbor)) true)))
85 * extension: graphing the propagator network
86 Draw a graph of a propagator network in a JFrame, tangles to
87 [[file:src/graphs.clj]].
89 #+begin_src clojure :tangle src/graphs.clj
92 (import '(javax.swing JFrame JPanel)
93 '(java.awt Color Graphics)
94 '(java.awt.image BufferedImage))
96 ;; saving graph information
97 ;; record keeping for graphing propagators
99 (defmacro remember-prop [name in-cells out-cells]
103 {:in-cells (map (fn [x#] (name x#)) (quote ~in-cells))
104 :out-cells (map (fn [x#] (name x#)) (quote ~out-cells))})))
106 (defmacro propagator "Define a new propagator."
107 [name in-cells out-cells & body]
109 (remember-prop ~name ~in-cells ~out-cells)
110 (defn ~(with-meta name
112 :in-cells in-cells :out-cells out-cells))
114 (doseq [cell# ~in-cells] (add-neighbor cell# ~name))
117 ;; stuff for graphing
120 (def frame (JFrame.))
122 (defn view "Display a graph generated by vijual" [img]
124 width (* (.getWidth img) mult)
125 height (* (.getHeight img) mult)
127 panel (doto (proxy [JPanel] []
129 (.drawImage g img offset offset nil))))]
130 (doto frame (.add panel) .pack (.setSize (+ offset width) (+ offset height)).show)))
132 (defn graph-propagators []
136 (let [hsh (get prop-graph key)]
138 (map #(vec (list % (name key))) (get hsh :in-cells))
139 (map #(vec (list (name key) %)) (get hsh :out-cells)))))
140 (keys prop-graph))))))
142 (defn view-network []
143 (view (draw-directed-graph-image (graph-propagators))))
145 (defn clear-network []
147 (def frame (JFrame.)))
151 These examples can be pasted into the repl.
153 ** doubling a number -- simplest
157 (propagator doubler [in-c] [out-c] (* 2 in))
158 ;; then any updates to in
160 ;; will propagate to out
164 ** square roots -- heron
165 #+begin_src clojure :tangle src/heron.clj
172 (propagator enough [x guess] [done]
174 (if (< (abs (- (* guess guess) x)) @margin) true false))
176 (propagator heron [x done guess] [guess]
180 (/ (+ guess (/ x guess)) 2.0)))
184 Alright, this will use Clojure's [[http://github.com/mmcgrana/ring][Ring]] web server middle-ware.
186 So, I guess the best demo here would be some reading/writing through a
189 The =app= will dispatch the incoming data to input cell by the route
190 at the end of the url, then there can be a couple of output cells
191 which will render different views of the related data.
193 #+begin_src clojure :tangle src/web.clj
194 (load-file "/home/eschulte/src/propagator/src/propagator.clj")
196 (use 'ring.adapter.jetty)
197 (use 'clojure.contrib.seq-utils)
198 (import 'java.util.Date 'java.text.SimpleDateFormat)
204 (propagator adder [input names] [names]
205 (when (> (count (seq input)) 0)
206 (set (cons (str input " :1") names))))
210 ;; page to accept input
211 (when (= "/" (:uri req))
213 :headers {"Content-Type" "text/html"
216 "<form name=\"\" action=\"add\" method=\"get\">"
217 "Word: <input type=\"type\" name=\"word\" />"
218 "<input type=\"submit\" value=\"Submit\" />"
220 ;; dump value into "input" cell
221 (when (re-matches #"/add" (:uri req))
222 (set-cell input (second (re-matches #".+=(.+)" (:query-string req))))
223 {:status 303 :headers {"Location" "../list"}})
224 ;; render the value of the "list" cell
225 (when-let [matched (re-matches #"/list" (:uri req))]
227 :headers {"Content-Type" "text/html"}
228 :body (apply str (flatten (list "<ul>"
229 (map #(str "<li>"%"</li>") @names)
231 "<p><a href=\"/\">another word</a></p>")))})))
233 (run-jetty #'app {:port 3000})
237 ** look at mutable data stores
238 - http://clojure.org/agents
239 - http://clojure.org/atoms
240 - http://clojure.org/refs
242 most definitely will use agents, functions to set their values are
243 applied to them with send (or send-off if potentially I/O bound), they
244 support validators, and they can be set to run actions (i.e. alert
245 propagators) as part of their update cycle (with add-watcher).
247 ** design to permit distribution across multiple machines
248 it should be possible to wrap these mutable items including
249 - network connections from other machines
250 - hardware (timers, I/O devices, etc...)
252 in cells, so that the propagator system remains "pure"
256 Copyright (C) 2010 Eric Schulte
258 This program is free software: you can redistribute it and/or modify
259 it under the terms of the GNU General Public License as published by
260 the Free Software Foundation, either version 3 of the License, or
261 (at your option) any later version.
263 This program is distributed in the hope that it will be useful,
264 but WITHOUT ANY WARRANTY; without even the implied warranty of
265 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
266 GNU General Public License for more details.
268 You should have received a [[file:COPYING][copy of the GNU General Public License]]
269 along with this program. If not, see <http://www.gnu.org/licenses/>.