hoplon / javelin

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

Lazy evaluation #27

Open dferens opened 8 years ago

dferens commented 8 years ago

Hi,

So basically there is no need to propagate updates to cells which have no listeners.

Imagine you have this cell structure and you are building app which tracks some other cell i. So, when a updates there is no need to compute cells e, f, g and h and we can defer that computation to deref call. javelin-1

Does it make sense? Thanks.

micha commented 8 years ago

@dferens Consider this situation (a common pattern in Hoplon applications):

(defc= org ...) ;; some formula cell

(defn login!
  "Maybe does AJAX request, or something..."
  [user pass org]
  ...)

;; The DOM:
(html
  (head)
  (body
    (let [user (cell nil)
          pass (cell nil)]
      (form
        :submit #(login! @user @pass @org)
        (label "User:" (input :keyup #(reset! user @%))) (br)
        (label "Pass:" (input :type "password" :keyup #(reset! pass @%)))))))

Notice how the org formula cell is dereferenced asynchronously, so there are no "listeners" on it. How would something like that work?

dferens commented 8 years ago

I'm sorry if I don't understand implementation of javelin properly. If org cell has no listeners on it, why can't we compute formula once on deref stage instead of computing it multiple times when some source cell updates?

micha commented 8 years ago

We could, but i believe the bookkeeping involved would actually make things slower.

micha commented 8 years ago

Also currently cells can contain state and perform side effects, like this, for instance:

(let [i-saw-a-dog (atom false)]
  (defc= c
    (do (when (= othercell "dog") (reset! i-saw-a-dog true))
        (str "hello, " othercell " i saw a dog is " @i-saw-a-dog))))

Lazy evaluation would make this kind of stateful cell impossible, because you might attach a listener or dereference after othercell has had the value of "dog" once but no longer does at the moment.

dferens commented 8 years ago

Can't we introduce another type of formula cell which exposes this lazy behaviour? Like:

(defc~ i (+ a 1))

And it will propagate its value only when there are some other cells watching it, or when some code is deref-ing it.

The goal of this stuff is to increase efficiency . If you have a lot of pure functions over some cell which simply transforms your data, you will compute just what you need.

fonghou commented 7 years ago

Just want to share a note that MobX (https://mobxjs.github.io/mobx/refguide/api.html, what a similarity to Javelin!) separates pure computed values (ie. pure formula cell) vs. reactions (ie. side-effecting formula cells). Hence, the former can be evaluated lazily, and the later eagerly.

Here is a blog by MobX's creator that explains the rational and what/how it accomplishes.

https://medium.com/@mweststrate/becoming-fully-reactive-an-in-depth-explanation-of-mobservable-55995262a254#.rprmqffz9

Cheers!

thedavidmeister commented 6 years ago

i think this is relevant https://github.com/hoplon/javelin/issues/37

rather than introduce a new but still hard-coded propagation mechanism, i'd personally like to see custom/extensible propagations implemented first, then a discussion of what specific propagation strategies should ship with core.

thedavidmeister commented 6 years ago

also this is spot on in the linked article above:

Any imperative action that an application takes in response to a state change usually creates or updates some values. In other words, most actions manage a local cache. Triggering the user interface to update? Updating aggregated values? Notifying the back-end? These can all be thought of as cache invalidations in disguise. To ensure these caches will stay in sync, you need to subscribe to future state changes that will enable your actions to be triggered again.

we can go back and forward on implementation details but everything ultimately boils down to cache management, which AFAIK has no truly general "one size fits all" solution.