hoplon / javelin

Spreadsheet-like dataflow programming in ClojureScript.
803 stars 44 forks source link

Cells created with literal data structures do not propagate updates #6

Closed cemerick closed 11 years ago

cemerick commented 11 years ago

(def root (cell {:a :b}))
; #<[object Object]>
;= nil
(def a (cell (:a root)))
; #<[object Object]>
;= nil
@a
;= :b
(swap! root assoc :a :c)
;= {:a :c}
@a
;= :b

Same interaction, but with the initial value taken "by reference":

(def start {:a :b})
;= {:a :b}
(def root (cell start))
; #<[object Object]>
;= nil
(def a  (cell (:a root)))
; #<[object Object]>
;= nil
@a
;= :b
(swap! root assoc :a :c)
;= {:a :c}
@root
;= {:a :c}
@a
;= :c
cemerick commented 11 years ago

Actually, the problem is more general; seems that many types of expressions produce a non-propagating cell:

(def root (cell (vec (range 4))))
; #<[object Object]>
;= nil
@root
;= [0 1 2 3]
(def cnt (cell (count root)))
; #<[object Object]>
;= nil
@cnt
;= 4
(swap! root conj 5)
;= [0 1 2 3 5]
@cnt
;= 4

The only sort of expression that appears to reliably work as an initial value is a symbol (naming an in-scope binding).

micha commented 11 years ago

In your example there you have created formula cells when you intended to make input cells. You can only swap! on input cells.

The short answer is:

(def root (cell '(vec (range 4))))

in your example above. Quoted expressions are evaluated in place and you obtain an input cell. Note that things like (cell {foo bar}) are transformed in the cell macro into things like (cell (hash-map foo bar)) and hash-map is then lifted, so if you are wanting an input cell containing a literal (non-reactive) vector, set, or map value then you must quote it. Note also that if you quote the expression then you'd need to explicitly deref foo or bar in that map if they are cells and you want the values they contain rather than the cells themselves.

The long answer follows in a minute...

micha commented 11 years ago

Consider the following:


(def vec (cell first))
;=> #<object Object>

(def root (cell (vec (range 4))))
;=> #<object Object>

@root
;=> 0

(reset! vec rest)
;=> #'cljs.core/rest

@root
;=> (1 2 3)

This is why you'd have to quote the expression.

cemerick commented 11 years ago

Got it, thanks. :-)

Idle suggestion: it might be clearer if input and formula cells had their own macros, at least for end-user consumption, since the compilation/evaluation semantics of each differ as you've described.

micha commented 10 years ago

Sorry I didn't see your response till just now...

There is actually the #'tailrecursion.javelin/input function that you can use to create an input cell, and then just use the cell macro for formula cells.