Hi All,
Timid coder here looking for some confirmation/advice on my
understanding of math and concurrent dataflow in Clojure before I dive
in.
Clojure looks like a great language and I am enthusiastic to try it
out on some light numerics programs. Basically, what I have now is
around a hundred equations with a dependency tree based on themselves
and 50 'input' variables in a pretty much standard time-stepped
simulation with lots of numerical integration, root-finding, and a
natural grouping of information into objects (not necessarily
synchronous with the world, however) . Presently, I already have the
math expressed in s-expression format, thanks to Maxima's handy
ability to export any equation as a lisp expression, thus saving a
great deal of time and typing after deriving things symbolically.
Previously when coding in common lisp, I tended to naively push my
data into an object, use with-slots to expose what variables I needed,
and then try to manage all my mutable state in a small number function
with a lot of SETF's that compute things in the proper order. Of
course, it was often tempting to recompute values on the fly than
figure out the mutable state dependency tree again, so there was lots
of wasted computation. At certain timesteps, I did not need to compute
the entire dependency tree at all, only a handful of values, yet I had
no easy way of doing this.
After I heard somebody (Rich Hickey?) say "Mutable data is the new
spaghetti code" I felt a tremendous sense of spiritual agreement and
decided to rethink how I would code my next simulation to exploit
clojure's useful idioms for concurrency and laziness. Dataflow
approaches seems natural for managing a tree of dependent expressions,
but I still was curious as to how people were managing lots of shared
symbols in an aesthetically clean way outside of that framework.
So, specifically I am searching for wisdom about:
1. How should I manage the shared symbolic context of a large number
of variables shared among multiple functions? (Is there an idiomatic
clojure way that is better than packaging all the stuff in a hashmap
and reimplementing something like common lisp's WITH-SLOTS?)
2. Am I right in understanding that contrib.dataflow evaluates eagerly
by default? Is there a rationale for this, or just nobody yet has
wanted (some or all) cells to be lazy by default?
3. Has anybody played around with adding concurrency to dataflow code
so that independent branches of the dependancy tree may be computed
simultaneously?
Happy hacking,
Ivar
;;; Some brain farts as I think about managing lots of symbols and
dataflow...
;;
;; Using the quadratic equation here purely as an example. Curious to
think about how
;; each of these approaches aesthetically scale to a blocks of math
with ~50 symbols.
(use 'clojure.contrib.math) ;; for sqrt
(def myhash {:a 1 :b 5 :c 6 :x nil})
;; The CL way of binding symbols was reasonably convenient,
;; but a with-slots equivalent doesn't seem to exist in clojure?
(defn some-eqn0 [obj]
(with-slots [a b c] obj
(/ (+ (- b) (sqrt (- (* b b) (* 4 a c))))
(* 2 a))))
;; Silly way #1
(defn some-eqn1 [obj]
(/ (+ (- (:b obj)) (sqrt (- (* (:b obj) (:obj b)) (* 4 (:a obj) (:c
obj)))))
(* 2 (:a obj))))
;; Silly way #2
(defn some-eqn2 [obj]
(let [a (:a obj)
b (:b obj)
c (:c obj)]
(/ (+ (- b) (sqrt (- (* b b) (* 4 a c))))
(* 2 a))))
;; Do things such as hash-map comprehensions exist? Should they? ;-)
(defn some-eqn3 [obj]
(let [{:a a :b b :c c} obj]
(/ (+ (- b) (sqrt (- (* b b) (* 4 a c))))
(* 2 a))))
;; What would using contrib.dataflow look like?
(use 'clojure.contrib.dataflow)
(def myobj {:df (build-dataflow [(cell :source a 1)
(cell :source b 5)
(cell :source c 6)])
:other "Maybe other, non-cell object data here too."})
(defn add-some-eqn4 [obj]
(let [df (:df obj)]
(add-cells df [(cell x (do (. Thread sleep 3000)
(println "X was (re)computed")
(/ (+ (- ?b) (sqrt (- (* ?b ?b) (* 4 ?a ?c))))
(* 2 ?a))
))
(cell y (* -1 ?x))])))
(add-some-eqn4 myobj)
(print-dataflow (:df myobj))
;; Playing around with watchers on x and y, it seems like dataflow
;; is not lazy by default, and certainly not lazy with watchers
(get-value (:df myobj) 'x) ;; returns -2
(get-value (:df myobj) 'y) ;; returns 2
(add-cell-watcher (get-cell (:df myobj) 'x)
nil
(fn [key cell o n]
(printf "x: %s -> %s\n" o n)))
(add-cell-watcher (get-cell (:df myobj) 'y)
nil
(fn [key cell o n]
(printf "y: %s -> %s\n" o n)))
(update-values (:df myobj)
{'a 1
'b -6
'c 9})
(update-values (:df myobj)
{'a 1
'b 5
'c 6})
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en