Reputation: 21801
I am attempting to build out the concept of a Cursor in clojurescript, backed by an atom. A cursor is a recursive zipper like mechanism for editing an immutable nested associated datastructure.
I am very newbie at Clojure, can you help me spot my errors?
(defprotocol Cursor
(refine [this path])
(set [this value])
(value [this]))
(defn- build-cursor* [state-atom paths]
(reify Cursor
(set [this value] (swap! state-atom (assoc-in @state-atom paths value)))
(refine [this path] (build-cursor* state-atom (conj paths path)))
(value [this] (get-in @state-atom paths))))
(defn build-cursor [state-atom]
(build-cursor* state-atom []))
(comment
(def s (atom {:a 42}))
(def c (build-cursor s))
(assert (= (value c) {:a 42}))
(set c {:a 43}) ;; WARNING: Wrong number of args (2) passed to quiescent-json-editor.core/set at line 1 <cljs repl>
(assert (= (value c) {:a 43}))
(def ca (refine c :a)) ;; WARNING: Wrong number of args (2) passed to quiescent-json-editor.core/refine at line 1 <cljs repl>
(assert (= (value ca) 43))
(set ca 44)
(assert (= (value ca) 43))
)
Upvotes: 2
Views: 177
Reputation: 11154
I am new to Clojure as well, but I took a stab at it and found two issues.
First, the set
method clashes with the core library function (even though it's in the Cursor
protocol). For the sake of debugging, I added underscore prefixes to avoid this.
Second, it seems that calling _set
on a root cursor corrupts the value. I found that assoc-in does not handle an empty path []
the way you might expect:
(assoc-in {} [] {:a 7})
; {nil {:a 7}}
...so that's the reason for the cond
in _set
.
Here is my test code:
(ns cursory)
(defprotocol Cursor
(_path [this])
(_root [this])
(_refine [this path])
(_set [this value])
(_value [this]))
(defn- build-cursor* [state-atom paths]
(reify Cursor
(_path [this] paths)
(_root [this] @state-atom)
(_set [this value] (cond
(empty? paths) (reset! state-atom value)
:else (assoc-in @state-atom paths value)))
(_refine [this path] (build-cursor* state-atom (conj paths path)))
(_value [this] (get-in @state-atom paths))))
(defn build-cursor [state-atom]
(build-cursor* state-atom []))
(comment
(def s (atom {:a 42, :b 84}))
(def c (build-cursor s))
(_set c {:a 44, :b 88})
(_root c)
(_path c)
(_value c)
(def ca (_refine c :a))
(_path ca)
(_value ca)
(_set ca 42)
(get-in {:a 1 :b 2} [])
(assoc-in {} [:a :b] 7)
(assoc-in {} [] {:a 7})
(empty? [])
)
Upvotes: 1