diff --git a/README.md b/README.md index b63e7d1..15cf831 100644 --- a/README.md +++ b/README.md @@ -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: ```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}]) [{: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 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: @@ -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. -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 -(def precompiled (comp-structure-paths ALL :a even?)) +(def precompiled (comp-paths ALL :a even?)) (update [ALL :a even?] structure) (update precompiled structure) diff --git a/src/clj/com/rpl/specter.clj b/src/clj/com/rpl/specter.clj index dc848e2..399adfd 100644 --- a/src/clj/com/rpl/specter.clj +++ b/src/clj/com/rpl/specter.clj @@ -6,13 +6,13 @@ ;;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 ;;beforehand -(defn comp-structure-paths [& structure-paths] - (comp-structure-paths* (vec structure-paths))) +(defn comp-paths [& paths] + (comp-paths* (vec paths))) ;; Selector functions (defn select [selector structure] - (let [sp (comp-structure-paths* selector)] + (let [sp (comp-paths* selector)] (select-full* sp [] structure @@ -45,7 +45,7 @@ ;; Update functions (defn update [selector update-fn structure] - (let [selector (comp-structure-paths* selector)] + (let [selector (comp-paths* selector)] (update-full* selector [] structure @@ -80,7 +80,7 @@ (def ALL (->AllStructurePath)) -(def VAL (->ValStructurePath)) +(def VAL (->ValCollect)) (def LAST (->LastStructurePath)) @@ -107,6 +107,18 @@ (defmacro viewfn [& 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 StructurePath (select* [kw structure next-fn] @@ -125,8 +137,8 @@ (next-fn structure) structure))) -(defn val-selector [& selector] - (->SelectorValsPath select (comp-structure-paths* selector))) +(defn collect [& selector] + (->SelectCollector select (comp-paths* selector))) -(defn val-selector-one [& selector] - (->SelectorValsPath select-one (comp-structure-paths* selector))) +(defn collect-one [& selector] + (->SelectCollector select-one (comp-paths* selector))) diff --git a/src/clj/com/rpl/specter/impl.clj b/src/clj/com/rpl/specter/impl.clj index 5ca3064..4e66019 100644 --- a/src/clj/com/rpl/specter/impl.clj +++ b/src/clj/com/rpl/specter/impl.clj @@ -17,13 +17,13 @@ com.rpl.specter.protocols.StructureValsPath (coerce-path [this] this) - com.rpl.specter.protocols.ValPath - (coerce-path [valpath] + com.rpl.specter.protocols.Collector + (coerce-path [collector] (reify StructureValsPath (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] - (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 Object @@ -37,12 +37,12 @@ ) -(extend-protocol StructurePathComposer +(extend-protocol StructureValsPathComposer Object - (comp-structure-paths* [sp] + (comp-paths* [sp] (coerce-path sp)) java.util.List - (comp-structure-paths* [structure-paths] + (comp-paths* [structure-paths] (reduce (fn [sp-curr sp] (reify StructureValsPath (select-full* [this vals structure next-fn] @@ -182,9 +182,9 @@ (->> structure (r/map next-fn) (into empty-structure)) )))) -(deftype ValStructurePath [] - ValPath - (select-val [this structure] +(deftype ValCollect [] + Collector + (collect-val [this structure] structure)) (deftype LastStructurePath [] @@ -237,9 +237,9 @@ (key-update akey structure next-fn) )) -(deftype SelectorValsPath [sel-fn selector] - ValPath - (select-val [this structure] +(deftype SelectCollector [sel-fn selector] + Collector + (collect-val [this structure] (sel-fn selector structure))) (deftype SRangePath [start-fn end-fn] @@ -269,3 +269,6 @@ (update* [this structure next-fn] (-> structure view-fn next-fn) )) + + + diff --git a/src/clj/com/rpl/specter/protocols.clj b/src/clj/com/rpl/specter/protocols.clj index c7cfea9..41d78ac 100644 --- a/src/clj/com/rpl/specter/protocols.clj +++ b/src/clj/com/rpl/specter/protocols.clj @@ -9,8 +9,8 @@ (select* [this structure next-fn]) (update* [this structure next-fn])) -(defprotocol ValPath - (select-val [this structure])) +(defprotocol Collector + (collect-val [this structure])) -(defprotocol StructurePathComposer - (comp-structure-paths* [structure-paths])) +(defprotocol StructureValsPathComposer + (comp-paths* [paths])) diff --git a/test/clj/com/rpl/specter/core_test.clj b/test/clj/com/rpl/specter/core_test.clj index 4576532..b892053 100644 --- a/test/clj/com/rpl/specter/core_test.clj +++ b/test/clj/com/rpl/specter/core_test.clj @@ -117,7 +117,7 @@ kw2 gen/keyword m (max-size 10 (gen-map-with-keys gen/keyword gen/int kw1 kw2)) pred (gen/elements [odd? even?])] - (= (update [(val-selector-one kw2) kw1 pred] + m) + (= (update [(collect-one kw2) kw1 pred] + m) (if (pred (kw1 m)) (assoc m kw1 (+ (kw1 m) (kw2 m))) m @@ -208,7 +208,7 @@ (deftest structure-path-directly-test (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 @@ -220,3 +220,10 @@ (afn 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] []] + ))))