From 3dc7ad25ff473dcf83aea29c8b35e79c800294b2 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Fri, 10 Jun 2016 07:57:18 -0400 Subject: [PATCH 1/5] added collected? and DISPENSE navigators --- CHANGES.md | 2 ++ src/clj/com/rpl/specter.cljx | 5 ++++ src/clj/com/rpl/specter/impl.cljx | 30 ++++++++++++++++++++ src/clj/com/rpl/specter/macros.clj | 8 ++++++ test/com/rpl/specter/core_test.cljx | 43 +++++++++++++++++++++++++++-- 5 files changed, 86 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3c2e044..343afbc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,8 @@ * Added `selected-any?` operation that returns true if any element is navigated to. * Huge performance improvements to `select`, `select-one`, `select-first`, and `select-one!` * Added META navigator (thanks @aengelberg) +* Added DISPENSE navigator to drop all collected values for subsequent navigation +* Added `collected?` macro to create a filter function which operates on the collected values * Bug fix: END, BEGINNING, FIRST, and LAST now work properly on nil ## 0.11.2 diff --git a/src/clj/com/rpl/specter.cljx b/src/clj/com/rpl/specter.cljx index 9d185ac..2368552 100644 --- a/src/clj/com/rpl/specter.cljx +++ b/src/clj/com/rpl/specter.cljx @@ -564,6 +564,11 @@ (collect-val [this structure] val )) +(def + ^{:doc "Drops all collected values for subsequent navigation."} + DISPENSE i/DISPENSE*) + + (defpathedfn if-path "Like cond-path, but with if semantics." ([cond-p then-path] diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index ffddf99..2bce1a1 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -609,6 +609,36 @@ ret )))) + +(def collected?* + (->ParamsNeededPath + (->TransformFunctions + RichPathExecutor + (fn [params params-idx vals structure next-fn] + (let [afn (aget ^objects params params-idx)] + (if (afn vals) + (next-fn params (inc params-idx) vals structure) + NONE + ))) + (fn [params params-idx vals structure next-fn] + (let [afn (aget ^objects params params-idx)] + (if (afn vals) + (next-fn params (inc params-idx) vals structure) + structure + )))) + 1 + )) + +(def DISPENSE* + (no-params-compiled-path + (->TransformFunctions + RichPathExecutor + (fn [params params-idx vals structure next-fn] + (next-fn params params-idx [] structure)) + (fn [params params-idx vals structure next-fn] + (next-fn params params-idx [] structure)) + ))) + (defn transform-fns-field [^CompiledPath path] (.-transform-fns path)) diff --git a/src/clj/com/rpl/specter/macros.clj b/src/clj/com/rpl/specter/macros.clj index c87a29e..c5e5c49 100644 --- a/src/clj/com/rpl/specter/macros.clj +++ b/src/clj/com/rpl/specter/macros.clj @@ -611,3 +611,11 @@ [apath transform-fn structure & args] `(i/compiled-replace-in* (path ~apath) ~transform-fn ~structure ~@args)) +(defmacro collected? + "Creates a filter function navigator that takes in all the collected values + as input. For arguments, can use `(collected? [a b] ...)` syntax to look + at each collected value as individual arguments, or `(collected? v ...)` syntax + to capture all the collected values as a single vector." + [params & body] + `(i/collected?* (fn [~params] ~@body)) + ) diff --git a/test/com/rpl/specter/core_test.cljx b/test/com/rpl/specter/core_test.cljx index a7e4916..666c190 100644 --- a/test/com/rpl/specter/core_test.cljx +++ b/test/com/rpl/specter/core_test.cljx @@ -8,7 +8,7 @@ :refer [paramsfn defprotocolpath defnav extend-protocolpath nav declarepath providepath select select-one select-one! select-first transform setval replace-in defnavconstructor - select-any selected-any?]]) + select-any selected-any? collected?]]) (:use #+clj [clojure.test :only [deftest is]] #+clj [clojure.test.check.clojure-test :only [defspec]] @@ -17,7 +17,7 @@ :only [paramsfn defprotocolpath defnav extend-protocolpath nav declarepath providepath select select-one select-one! select-first transform setval replace-in defnavconstructor - select-any selected-any?]] + select-any selected-any? collected?]] ) @@ -1201,3 +1201,42 @@ (is (empty? (select s/LAST nil))) (is (empty? (select s/ALL nil))) ) + +(defspec dispense-test + (for-all+ + [k1 gen/int + k2 gen/int + k3 gen/int + m (gen-map-with-keys gen/int gen/int k1 k2 k3)] + (= (select [(s/collect-one (s/keypath k1)) + (s/collect-one (s/keypath k2)) + s/DISPENSE + (s/collect-one (s/keypath k2)) + (s/keypath k3)] + m) + (select [(s/collect-one (s/keypath k2)) + (s/keypath k3)] + m) + ))) + +(deftest collected?-test + (let [data {:active-id 1 :items [{:id 1 :name "a"} {:id 2 :name "b"}]}] + (is (= {:id 1 :name "a"} + (select-any [(s/collect-one :active-id) + :items + s/ALL + (s/collect-one :id) + (collected? [a i] (= a i)) + s/DISPENSE + ] + data) + (select-any [(s/collect-one :active-id) + :items + s/ALL + (s/collect-one :id) + (collected? v (apply = v)) + s/DISPENSE + ] + data) + )) + )) From 589e6aa47145c9e9ea7af6602ae3a3d46c1a8fcd Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Fri, 10 Jun 2016 08:09:11 -0400 Subject: [PATCH 2/5] added a collected?/DISPENSE test case for transform path --- test/com/rpl/specter/core_test.cljx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/test/com/rpl/specter/core_test.cljx b/test/com/rpl/specter/core_test.cljx index 666c190..2e7511c 100644 --- a/test/com/rpl/specter/core_test.cljx +++ b/test/com/rpl/specter/core_test.cljx @@ -1238,5 +1238,16 @@ s/DISPENSE ] data) - )) - )) + ))) + (let [data {:active 3 :items [{:id 1 :val 0} {:id 3 :val 11}]}] + (is (= (transform [:items s/ALL (s/selected? :id #(= % 3)) :val] inc data) + (transform [(s/collect-one :active) + :items + s/ALL + (s/collect-one :id) + (collected? [a i] (= a i)) + s/DISPENSE + :val] + inc + data) + )))) From f26aa001ebc8ae2bd8d4dcf03635931c6fb2a516 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Sat, 11 Jun 2016 09:51:28 -0400 Subject: [PATCH 3/5] make MAP-VALS work on nil --- src/clj/com/rpl/specter/impl.cljx | 5 +++++ test/com/rpl/specter/core_test.cljx | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index 2bce1a1..f440efa 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -907,6 +907,11 @@ structure)) (extend-protocol MapValsTransformProtocol + nil + (map-vals-transform [structure next-fn] + nil + ) + #+clj clojure.lang.PersistentArrayMap #+cljs cljs.core/PersistentArrayMap (map-vals-transform [structure next-fn] (map-vals-non-transient-transform structure {} next-fn) diff --git a/test/com/rpl/specter/core_test.cljx b/test/com/rpl/specter/core_test.cljx index 2e7511c..b4cb852 100644 --- a/test/com/rpl/specter/core_test.cljx +++ b/test/com/rpl/specter/core_test.cljx @@ -1202,6 +1202,11 @@ (is (empty? (select s/ALL nil))) ) +(deftest map-vals-nil + (is (= nil (transform s/MAP-VALS inc nil))) + (is (empty? (select s/MAP-VALS nil))) + ) + (defspec dispense-test (for-all+ [k1 gen/int From eaa1220af91f62f2b850fc31fd55ee550d8ae887 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Sat, 11 Jun 2016 09:51:52 -0400 Subject: [PATCH 4/5] update changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 343afbc..a57fa18 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,7 @@ * Added META navigator (thanks @aengelberg) * Added DISPENSE navigator to drop all collected values for subsequent navigation * Added `collected?` macro to create a filter function which operates on the collected values -* Bug fix: END, BEGINNING, FIRST, and LAST now work properly on nil +* Bug fix: END, BEGINNING, FIRST, LAST, and MAP-VALS now work properly on nil ## 0.11.2 * Renamed com.rpl.specter.transient namespace to com.rpl.specter.transients to eliminate ClojureScript compiler warning about reserved keyword From fd180d2e2cae6d689e10da03bc25e2607dd9758e Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Sun, 12 Jun 2016 09:44:49 -0400 Subject: [PATCH 5/5] link to new wiki page --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a5d1229..1863498 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,7 @@ The latest release version of Specter is hosted on [Clojars](https://clojars.org - Introductory blog post: [Functional-navigational programming in Clojure(Script) with Specter](http://nathanmarz.com/blog/functional-navigational-programming-in-clojurescript-with-sp.html) - Presentation about Specter: [Specter: Powerful and Simple Data Structure Manipulation](https://www.youtube.com/watch?v=VTCy_DkAJGk) +- List of navigators with examples: [This wiki page](https://github.com/nathanmarz/specter/wiki/List-of-Navigators) provides a more comprehensive overview than the API docs about the behavior of specific navigators and includes many examples. - [API docs](http://nathanmarz.github.io/specter/) - Performance guide: The [Specter 0.11.0 announcement post](https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs) provides a comprehensive overview of how Specter achieves its performance and what you need to know as a user to enable Specter to perform its optimizations.