Dustin Getz
Dustin Getz

Reputation: 21801

Clojure newbie struggling with protocols

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

Answers (1)

lnmx
lnmx

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

Related Questions