change val-select* to collect*, rename comp-structure-paths to comp-paths, clean up names of protocols, add selected? function, update README

This commit is contained in:
Nathan Marz 2015-04-19 13:45:20 -04:00
parent ea05b98280
commit 45260ff9c1
5 changed files with 54 additions and 32 deletions

View file

@ -85,7 +85,7 @@ user> (select (walker number?)
When doing more involved transformations, you often find you lose context when navigating deep within a data structure and need information "up" the data structure to perform the transformation. Specter solves this problem by allowing you to collect values during navigation to use in the update function. Here's an example which transforms a sequence of maps by adding the value of the :b key to the value of the :a key, but only if the :a key is even: When doing more involved transformations, you often find you lose context when navigating deep within a data structure and need information "up" the data structure to perform the transformation. Specter solves this problem by allowing you to collect values during navigation to use in the update function. Here's an example which transforms a sequence of maps by adding the value of the :b key to the value of the :a key, but only if the :a key is even:
```clojure ```clojure
user> (update [ALL (val-selector-one :b) :a even?] user> (update [ALL (collect-one :b) :a even?]
+ +
[{:a 1 :b 3} {:a 2 :b -10} {:a 4 :b 10} {:a 3}]) [{:a 1 :b 3} {:a 2 :b -10} {:a 4 :b 10} {:a 3}])
[{:b 3, :a 1} {:b -10, :a -8} {:b 10, :a 14} {:a 3}] [{:b 3, :a 1} {:b -10, :a -8} {:b 10, :a 14} {:a 3}]
@ -93,7 +93,7 @@ user> (update [ALL (val-selector-one :b) :a even?]
The update function receives as arguments all the collected values followed by the navigated to value. So in this case `+` receives the value of the :b key followed by the value of the :a key, and the update is performed to :a's value. The update function receives as arguments all the collected values followed by the navigated to value. So in this case `+` receives the value of the :b key followed by the value of the :a key, and the update is performed to :a's value.
The three built-in ways for collecting values are `VAL`, `val-selector`, and `val-selector-one`. `VAL` just adds whatever element it's currently on to the value list, while `val-selector` and `val-selector-one` take in a selector to navigate to the desired value. `val-selector` works just like `select` by finding a sequence of values, while `val-selector-one` expects to only navigate to a single value. The three built-in ways for collecting values are `VAL`, `collect`, and `collect-one`. `VAL` just adds whatever element it's currently on to the value list, while `collect` and `collect-one` take in a selector to navigate to the desired value. `collect` works just like `select` by finding a sequence of values, while `collect-one` expects to only navigate to a single value.
To make your own selector, implement the `StructurePath` protocol which looks like: To make your own selector, implement the `StructurePath` protocol which looks like:
@ -106,10 +106,10 @@ To make your own selector, implement the `StructurePath` protocol which looks li
Looking at the implementations of the built-in selectors should provide you with the guidance you need to make your own selectors. Looking at the implementations of the built-in selectors should provide you with the guidance you need to make your own selectors.
Finally, you can make `select` and `update` work much faster by precompiling your selectors using the `comp-structure-paths` function. There's about a 5x speed difference between the following two invocations of update: Finally, you can make `select` and `update` work much faster by precompiling your selectors using the `comp-paths` function. There's about a 5x speed difference between the following two invocations of update:
```clojure ```clojure
(def precompiled (comp-structure-paths ALL :a even?)) (def precompiled (comp-paths ALL :a even?))
(update [ALL :a even?] structure) (update [ALL :a even?] structure)
(update precompiled structure) (update precompiled structure)

View file

@ -6,13 +6,13 @@
;;there are going to be. this should make it much easier to allocate space for vals without doing concats ;;there are going to be. this should make it much easier to allocate space for vals without doing concats
;;all over the place. The apply to the vals + structure can also be avoided since the number of vals is known ;;all over the place. The apply to the vals + structure can also be avoided since the number of vals is known
;;beforehand ;;beforehand
(defn comp-structure-paths [& structure-paths] (defn comp-paths [& paths]
(comp-structure-paths* (vec structure-paths))) (comp-paths* (vec paths)))
;; Selector functions ;; Selector functions
(defn select [selector structure] (defn select [selector structure]
(let [sp (comp-structure-paths* selector)] (let [sp (comp-paths* selector)]
(select-full* sp (select-full* sp
[] []
structure structure
@ -45,7 +45,7 @@
;; Update functions ;; Update functions
(defn update [selector update-fn structure] (defn update [selector update-fn structure]
(let [selector (comp-structure-paths* selector)] (let [selector (comp-paths* selector)]
(update-full* selector (update-full* selector
[] []
structure structure
@ -80,7 +80,7 @@
(def ALL (->AllStructurePath)) (def ALL (->AllStructurePath))
(def VAL (->ValStructurePath)) (def VAL (->ValCollect))
(def LAST (->LastStructurePath)) (def LAST (->LastStructurePath))
@ -107,6 +107,18 @@
(defmacro viewfn [& args] (defmacro viewfn [& args]
`(view (fn ~@args))) `(view (fn ~@args)))
(defn selected?
"Filters the current value based on whether a selector finds anything.
e.g. (selected? :vals ALL even?) keeps the current element only if an
even number exists for the :vals key"
[& selectors]
(let [s (comp-paths selectors)]
(fn [structure]
(->> structure
(select s)
empty?
not))))
(extend-type clojure.lang.Keyword (extend-type clojure.lang.Keyword
StructurePath StructurePath
(select* [kw structure next-fn] (select* [kw structure next-fn]
@ -125,8 +137,8 @@
(next-fn structure) (next-fn structure)
structure))) structure)))
(defn val-selector [& selector] (defn collect [& selector]
(->SelectorValsPath select (comp-structure-paths* selector))) (->SelectCollector select (comp-paths* selector)))
(defn val-selector-one [& selector] (defn collect-one [& selector]
(->SelectorValsPath select-one (comp-structure-paths* selector))) (->SelectCollector select-one (comp-paths* selector)))

View file

@ -17,13 +17,13 @@
com.rpl.specter.protocols.StructureValsPath com.rpl.specter.protocols.StructureValsPath
(coerce-path [this] this) (coerce-path [this] this)
com.rpl.specter.protocols.ValPath com.rpl.specter.protocols.Collector
(coerce-path [valpath] (coerce-path [collector]
(reify StructureValsPath (reify StructureValsPath
(select-full* [this vals structure next-fn] (select-full* [this vals structure next-fn]
(next-fn (conj vals (select-val valpath structure)) structure)) (next-fn (conj vals (collect-val collector structure)) structure))
(update-full* [this vals structure next-fn] (update-full* [this vals structure next-fn]
(next-fn (conj vals (select-val valpath structure)) structure)))) (next-fn (conj vals (collect-val collector structure)) structure))))
;; need to say Object instead of StructurePath so that things like Keyword are properly coerced ;; need to say Object instead of StructurePath so that things like Keyword are properly coerced
Object Object
@ -37,12 +37,12 @@
) )
(extend-protocol StructurePathComposer (extend-protocol StructureValsPathComposer
Object Object
(comp-structure-paths* [sp] (comp-paths* [sp]
(coerce-path sp)) (coerce-path sp))
java.util.List java.util.List
(comp-structure-paths* [structure-paths] (comp-paths* [structure-paths]
(reduce (fn [sp-curr sp] (reduce (fn [sp-curr sp]
(reify StructureValsPath (reify StructureValsPath
(select-full* [this vals structure next-fn] (select-full* [this vals structure next-fn]
@ -182,9 +182,9 @@
(->> structure (r/map next-fn) (into empty-structure)) (->> structure (r/map next-fn) (into empty-structure))
)))) ))))
(deftype ValStructurePath [] (deftype ValCollect []
ValPath Collector
(select-val [this structure] (collect-val [this structure]
structure)) structure))
(deftype LastStructurePath [] (deftype LastStructurePath []
@ -237,9 +237,9 @@
(key-update akey structure next-fn) (key-update akey structure next-fn)
)) ))
(deftype SelectorValsPath [sel-fn selector] (deftype SelectCollector [sel-fn selector]
ValPath Collector
(select-val [this structure] (collect-val [this structure]
(sel-fn selector structure))) (sel-fn selector structure)))
(deftype SRangePath [start-fn end-fn] (deftype SRangePath [start-fn end-fn]
@ -269,3 +269,6 @@
(update* [this structure next-fn] (update* [this structure next-fn]
(-> structure view-fn next-fn) (-> structure view-fn next-fn)
)) ))

View file

@ -9,8 +9,8 @@
(select* [this structure next-fn]) (select* [this structure next-fn])
(update* [this structure next-fn])) (update* [this structure next-fn]))
(defprotocol ValPath (defprotocol Collector
(select-val [this structure])) (collect-val [this structure]))
(defprotocol StructurePathComposer (defprotocol StructureValsPathComposer
(comp-structure-paths* [structure-paths])) (comp-paths* [paths]))

View file

@ -117,7 +117,7 @@
kw2 gen/keyword kw2 gen/keyword
m (max-size 10 (gen-map-with-keys gen/keyword gen/int kw1 kw2)) m (max-size 10 (gen-map-with-keys gen/keyword gen/int kw1 kw2))
pred (gen/elements [odd? even?])] pred (gen/elements [odd? even?])]
(= (update [(val-selector-one kw2) kw1 pred] + m) (= (update [(collect-one kw2) kw1 pred] + m)
(if (pred (kw1 m)) (if (pred (kw1 m))
(assoc m kw1 (+ (kw1 m) (kw2 m))) (assoc m kw1 (+ (kw1 m) (kw2 m)))
m m
@ -208,7 +208,7 @@
(deftest structure-path-directly-test (deftest structure-path-directly-test
(is (= 3 (select-one :b {:a 1 :b 3}))) (is (= 3 (select-one :b {:a 1 :b 3})))
(is (= 5 (select-one (comp-structure-paths :a :b) {:a {:b 5}}))) (is (= 5 (select-one (comp-paths :a :b) {:a {:b 5}})))
) )
(defspec view-test (defspec view-test
@ -220,3 +220,10 @@
(afn i) (afn i)
(update (view afn) identity i) (update (view afn) identity i)
))) )))
(deftest selected?-test
(is (= [[1 3 5] [2 :a] [7 11 4 2 :a] [10 1 :a] []]
(setval [ALL (selected? ALL even?) END]
[:a]
[[1 3 5] [2] [7 11 4 2] [10 1] []]
))))