vrac

Template-based web library [WIP] - https://github.com/green-coder/vrac Zulip archive: https://clojurians.zulipchat.com/#narrow/stream/180378-slack-archive/topic/vrac Clojureverse archive: https://clojurians-log.clojureverse.org/vrac
2020-09-03T03:08:21.422Z

The idea is to have a structure of subscribers on the different parts (different paths) of the db via a hierarchy of hashmaps which list sets of subscribers in its nodes. I call this structure a subscription-tree. That is probably not a new thing, I guess reagent has something like that. The part which might be new is to have a subscription-tree on each node of the compute graph. It means that the action of listening changes on a part of a data is a generalized and homogenous feature in Vrac. The selection of the right subscribers is fast when done based on a diff. An since each compute node is going to return a diff, everything should work well and efficiently.

2020-09-03T03:12:05.425100Z

Precision: the diff approach is only related to structural changes. Most operations which will be in the compute node (like first-name + last-name -> full-name) won’t really need it, and will still be written as a classical function implementation which does not output diffs. Those functions will be wrapped to fit in the compute graph.

2020-09-03T03:15:33.427600Z

Thanks to the subscription-tree, the template below will use zero compute nodes and will feed directly on the client db through this structure:

;; Subscription-tree on the client db:
{:children {:user {:children {:first-name {:subscribers #{1}}
                              :last-name {:subscribers #{2}}}}}}

;; Subscribing template:
(let [user (:user global)
      {:keys [first-name last-name]} user]
  [:div first-name " " last-name])

2020-09-03T03:20:39.428700Z

I am going to start implementing a (slow) reference version of the compute graph, as a proof-of-concept of the algorithms and diff-flow.

2020-09-03T03:49:48.429500Z

Here is a bit of the impl:

(def empty-subscription-tree {})

(defn subscribe-on-path [tree path subscriber]
  (if (seq path)
    (let [[path-element & path-rest] path]
      (update-in tree [:children path-element] subscribe-on-path path-rest subscriber))
    (update tree :subscribers (fnil conj #{}) subscriber)))

(defn- update-coll-then-dissoc-empty [coll key f & args]
  (let [new-val (apply f (get coll key) args)]
    (if (seq new-val) ; new-val is assumed to be a collection
      (assoc coll key new-val)
      (dissoc coll key))))

(defn unsubscribe-from-path [tree path subscriber]
  (if (seq path)
    (let [[path-element & path-rest] path]
      (update-coll-then-dissoc-empty tree :children
        update-coll-then-dissoc-empty path-element
        unsubscribe-from-path path-rest subscriber))
    (update-coll-then-dissoc-empty tree :subscribers disj subscriber)))

;; Testing
#_ (-> empty-subscription-tree
       (subscribe-on-path [:user :first-name] 1)
       (subscribe-on-path [:user :last-name] 2))

; => {:children {:user {:children {:first-name {:subscribers #{1}}
;                                  :last-name {:subscribers #{2}}}}}}

#_ (-> *1
       (unsubscribe-from-path [:user :first-name] 1)
       (unsubscribe-from-path [:user :last-name] 2))
; => {}

2020-09-03T03:53:59.430600Z

— End of today’s brainstorming for me.