From b3e581f7371a96b8cb2098ce2e7049349508af4e Mon Sep 17 00:00:00 2001 From: Alex Engelberg Date: Sun, 5 Jun 2016 00:46:21 -0700 Subject: [PATCH 01/27] WIP, transient navigators --- src/clj/com/rpl/specter/impl.cljx | 49 ++++++++++++++++ src/clj/com/rpl/specter/transient.cljx | 79 ++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 src/clj/com/rpl/specter/transient.cljx diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index 023390c..f5d5704 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -757,6 +757,55 @@ (next-fn structure) )) +(deftype TransientEndNavigator []) + +(extend-protocol p/Navigator + TransientEndNavigator + (select* [this structure next-fn] + []) + (transform* [this structure next-fn] + (let [res (next-fn [])] + (reduce conj! structure res)))) + +(deftype TransientLastNavigator []) + +(extend-protocol p/Navigator + TransientLastNavigator + (select* [this structure next-fn] + (next-fn (nth structure (dec (count structure))))) + (transform* [this structure next-fn] + (let [i (dec (count structure))] + (assoc! structure i (next-fn (nth structure i)))))) + +#+clj +(defn transient-all-select + [structure next-fn] + (into [] (r/mapcat #(next-fn (nth structure %)) + (range (count structure))))) + +#+cljs +(defn transient-all-select + [structure next-fn] + (into [] + (r/mapcat #(next-fn (nth structure %))) + (range (count structure)))) + +(defn transient-all-transform! + [structure next-fn] + (reduce (fn [structure i] + (assoc! structure i (next-fn (nth structure i)))) + structure + (range (count structure)))) + +(deftype TransientAllNavigator []) + +(extend-protocol p/Navigator + TransientAllNavigator + (select* [this structure next-fn] + (transient-all-select structure next-fn)) + (transform* [this structure next-fn] + (transient-all-transform! structure next-fn))) + (defn extract-basic-filter-fn [path] (cond (fn? path) path diff --git a/src/clj/com/rpl/specter/transient.cljx b/src/clj/com/rpl/specter/transient.cljx new file mode 100644 index 0000000..db0d1cd --- /dev/null +++ b/src/clj/com/rpl/specter/transient.cljx @@ -0,0 +1,79 @@ +(ns com.rpl.specter.transient + #+cljs + (:require-macros [com.rpl.specter.macros + :refer + [defnav + defpathedfn]]) + (:use #+clj + [com.rpl.specter.macros :only + [defnav + defpathedfn]]) + (:require [com.rpl.specter.impl :as i] + [com.rpl.specter :refer [subselect]])) + +(defnav + ^{:doc "Navigates to the specified key of a transient collection, + navigating to nil if it doesn't exist."} + keypath! + [key] + (select* [this structure next-fn] + (next-fn (get structure key))) + (transform* [this structure next-fn] + (assoc! structure key (next-fn (get structure key))))) + +(def END! + "Navigates to an empty (persistent) vector at the end of a transient vector." + (i/comp-paths* [(i/->TransientEndNavigator)])) + +(def FIRST! + "Navigates to the first element of a transient vector." + (keypath! 0)) + +(def LAST! + "Navigates to the last element of a transient vector." + (i/comp-paths* [(i/->TransientLastNavigator)])) + +(def ALL! + ;; TODO: only works on transient vectors, not sets / maps; need a + ;; different name? + "Navigates to each element of a transient vector." + (i/comp-paths* [(i/->TransientAllNavigator)])) + +(defpathedfn filterer! + "Navigates to a view of the current transient vector that only + contains elements that match the given path." + [& path] + (subselect ALL! (selected? path))) + +(defn- select-keys-from-transient-map + "Selects keys from transient map, because built-in select-keys uses + `find` which is unsupported." + [m m-keys] + (loop [result {} + m-keys m-keys] + (if-not (seq m-keys) + result + (let [k (first m-keys) + ;; support Clojure 1.6 where contains? is broken on transients + item (get m k ::not-found)] + (recur (if-not (identical? item ::not-found) + (assoc result k item) + result) + (rest m-keys)))))) + +(defnav + ^{:doc "Navigates to the specified persistent submap of a transient map."} + submap! + [m-keys] + (select* [this structure next-fn] + (select-keys-from-transient-map structure m-keys)) + (transform* [this structure next-fn] + (let [selected (select-keys-from-transient-map structure m-keys) + res (next-fn selected)] + (as-> structure % + (reduce (fn [m k] + (dissoc! m k)) + % m-keys) + (reduce (fn [m [k v]] + (assoc! m k v)) + % res))))) From 067ce9edee0ef4b73a264a048393517629f0a8b7 Mon Sep 17 00:00:00 2001 From: Alex Engelberg Date: Sun, 5 Jun 2016 11:10:40 -0700 Subject: [PATCH 02/27] Remove ALL! and filterer! --- src/clj/com/rpl/specter/impl.cljx | 9 --------- src/clj/com/rpl/specter/transient.cljx | 14 +------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index f5d5704..21f7b1f 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -797,15 +797,6 @@ structure (range (count structure)))) -(deftype TransientAllNavigator []) - -(extend-protocol p/Navigator - TransientAllNavigator - (select* [this structure next-fn] - (transient-all-select structure next-fn)) - (transform* [this structure next-fn] - (transient-all-transform! structure next-fn))) - (defn extract-basic-filter-fn [path] (cond (fn? path) path diff --git a/src/clj/com/rpl/specter/transient.cljx b/src/clj/com/rpl/specter/transient.cljx index db0d1cd..6cde71f 100644 --- a/src/clj/com/rpl/specter/transient.cljx +++ b/src/clj/com/rpl/specter/transient.cljx @@ -9,7 +9,7 @@ [defnav defpathedfn]]) (:require [com.rpl.specter.impl :as i] - [com.rpl.specter :refer [subselect]])) + [com.rpl.specter :refer [subselect selected?]])) (defnav ^{:doc "Navigates to the specified key of a transient collection, @@ -33,18 +33,6 @@ "Navigates to the last element of a transient vector." (i/comp-paths* [(i/->TransientLastNavigator)])) -(def ALL! - ;; TODO: only works on transient vectors, not sets / maps; need a - ;; different name? - "Navigates to each element of a transient vector." - (i/comp-paths* [(i/->TransientAllNavigator)])) - -(defpathedfn filterer! - "Navigates to a view of the current transient vector that only - contains elements that match the given path." - [& path] - (subselect ALL! (selected? path))) - (defn- select-keys-from-transient-map "Selects keys from transient map, because built-in select-keys uses `find` which is unsupported." From cb0dc261cf31d41f69465bacb082f03f2834c006 Mon Sep 17 00:00:00 2001 From: Alex Engelberg Date: Sun, 5 Jun 2016 21:38:14 -0700 Subject: [PATCH 03/27] Add tests for transients, fix transient navigators based on test failures --- src/clj/com/rpl/specter/impl.cljx | 23 +++++++++--------- src/clj/com/rpl/specter/transient.cljx | 23 +++++++++++++++--- test/com/rpl/specter/core_test.cljx | 32 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index 21f7b1f..26cbf4f 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -466,6 +466,14 @@ (defn vec-count [v] (count v)) +#+clj +(defn transient-vec-count [^clojure.lang.ITransientVector v] + (.count v)) + +#+cljs +(defn transient-vec-count [v] + (count v)) + (extend-protocol UpdateExtremes #+clj clojure.lang.PersistentVector #+cljs cljs.core/PersistentVector (update-first [v afn] @@ -505,6 +513,9 @@ #+clj clojure.lang.IPersistentVector #+cljs cljs.core/PersistentVector (fast-empty? [v] (= 0 (vec-count v))) + #+clj clojure.lang.ITransientVector #+cljs cljs.core/TransientVector + (fast-empty? [v] + (= 0 (transient-vec-count v))) #+clj Object #+cljs default (fast-empty? [s] (empty? s)) @@ -762,21 +773,11 @@ (extend-protocol p/Navigator TransientEndNavigator (select* [this structure next-fn] - []) + (next-fn [])) (transform* [this structure next-fn] (let [res (next-fn [])] (reduce conj! structure res)))) -(deftype TransientLastNavigator []) - -(extend-protocol p/Navigator - TransientLastNavigator - (select* [this structure next-fn] - (next-fn (nth structure (dec (count structure))))) - (transform* [this structure next-fn] - (let [i (dec (count structure))] - (assoc! structure i (next-fn (nth structure i)))))) - #+clj (defn transient-all-select [structure next-fn] diff --git a/src/clj/com/rpl/specter/transient.cljx b/src/clj/com/rpl/specter/transient.cljx index 6cde71f..304db07 100644 --- a/src/clj/com/rpl/specter/transient.cljx +++ b/src/clj/com/rpl/specter/transient.cljx @@ -25,13 +25,30 @@ "Navigates to an empty (persistent) vector at the end of a transient vector." (i/comp-paths* [(i/->TransientEndNavigator)])) +(defn- t-get-first + [tv] + (nth tv 0)) + +(defn- t-get-last + [tv] + (nth tv (dec (i/transient-vec-count tv)))) + +(defn- t-update-first + [tv next-fn] + (assoc! tv 0 (next-fn (nth tv 0)))) + +(defn- t-update-last + [tv next-fn] + (let [i (dec (i/transient-vec-count tv))] + (assoc! tv i (next-fn (nth tv i))))) + (def FIRST! "Navigates to the first element of a transient vector." - (keypath! 0)) + (i/->PosNavigator t-get-first t-update-first)) (def LAST! "Navigates to the last element of a transient vector." - (i/comp-paths* [(i/->TransientLastNavigator)])) + (i/->PosNavigator t-get-last t-update-last)) (defn- select-keys-from-transient-map "Selects keys from transient map, because built-in select-keys uses @@ -54,7 +71,7 @@ submap! [m-keys] (select* [this structure next-fn] - (select-keys-from-transient-map structure m-keys)) + (next-fn (select-keys-from-transient-map structure m-keys))) (transform* [this structure next-fn] (let [selected (select-keys-from-transient-map structure m-keys) res (next-fn selected)] diff --git a/test/com/rpl/specter/core_test.cljx b/test/com/rpl/specter/core_test.cljx index cf0fbb1..5e2d72b 100644 --- a/test/com/rpl/specter/core_test.cljx +++ b/test/com/rpl/specter/core_test.cljx @@ -25,6 +25,7 @@ #+cljs [cljs.test.check.generators :as gen] #+cljs [cljs.test.check.properties :as prop :include-macros true] [com.rpl.specter :as s] + [com.rpl.specter.transient :as t] [clojure.set :as set])) ;;TODO: @@ -1036,3 +1037,34 @@ (is (= 2 (key e))) (is (= 4 (val e))) )) + +(defspec transient-vector-test + (for-all+ + [v (gen/vector (limit-size 5 gen/int))] + (every? identity + (for [[path transient-path f] + [[s/FIRST t/FIRST! (fnil inc 0)] ;; fnil in case vector is empty + [s/LAST t/LAST! (fnil inc 0)] + [(s/keypath 0) (t/keypath! 0) (fnil inc 0)] + [s/END t/END! #(conj % 1 2 3)]]] + (and (= (s/transform* path f v) + (persistent! (s/transform* transient-path f (transient v)))) + (= (s/select* path v) + (s/select* transient-path (transient v)))))))) + +(defspec transient-map-test + (for-all+ + [m (gen/not-empty (gen/map gen/keyword gen/int)) + new-key gen/keyword] + (let [existing-key (first (keys m))] + (every? identity + (for [[path transient-path f] + [[(s/keypath existing-key) (t/keypath! existing-key) inc] + [(s/keypath new-key) (t/keypath! new-key) (constantly 3)] + [(s/submap [existing-key new-key]) + (t/submap! [existing-key new-key]) + (constantly {new-key 1234})]]] + (and (= (s/transform* path f m) + (persistent! (s/transform* transient-path f (transient m)))) + (= (s/select* path m) + (s/select* transient-path (transient m))))))))) From 25ba21d9ee42cefbde0b0d49a1605285cf03fb66 Mon Sep 17 00:00:00 2001 From: Alex Engelberg Date: Sun, 5 Jun 2016 21:47:44 -0700 Subject: [PATCH 04/27] Remove no-longer-used transient-all-select|transform --- src/clj/com/rpl/specter/impl.cljx | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index 26cbf4f..af1f7f3 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -778,26 +778,6 @@ (let [res (next-fn [])] (reduce conj! structure res)))) -#+clj -(defn transient-all-select - [structure next-fn] - (into [] (r/mapcat #(next-fn (nth structure %)) - (range (count structure))))) - -#+cljs -(defn transient-all-select - [structure next-fn] - (into [] - (r/mapcat #(next-fn (nth structure %))) - (range (count structure)))) - -(defn transient-all-transform! - [structure next-fn] - (reduce (fn [structure i] - (assoc! structure i (next-fn (nth structure i)))) - structure - (range (count structure)))) - (defn extract-basic-filter-fn [path] (cond (fn? path) path From 399e5661f115c49154efde8edaae0ff386720468 Mon Sep 17 00:00:00 2001 From: Alex Engelberg Date: Sun, 5 Jun 2016 22:11:34 -0700 Subject: [PATCH 05/27] The (identical?) trick doesn't work in cljs, but select-keys does --- src/clj/com/rpl/specter/transient.cljx | 7 +++++++ test/com/rpl/specter/core_test.cljx | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/clj/com/rpl/specter/transient.cljx b/src/clj/com/rpl/specter/transient.cljx index 304db07..ef15d54 100644 --- a/src/clj/com/rpl/specter/transient.cljx +++ b/src/clj/com/rpl/specter/transient.cljx @@ -50,6 +50,7 @@ "Navigates to the last element of a transient vector." (i/->PosNavigator t-get-last t-update-last)) +#+clj (defn- select-keys-from-transient-map "Selects keys from transient map, because built-in select-keys uses `find` which is unsupported." @@ -66,6 +67,12 @@ result) (rest m-keys)))))) +#+cljs +(defn- select-keys-from-transient-map + "Uses select-keys on a transient map." + [m m-keys] + (select-keys m m-keys)) + (defnav ^{:doc "Navigates to the specified persistent submap of a transient map."} submap! diff --git a/test/com/rpl/specter/core_test.cljx b/test/com/rpl/specter/core_test.cljx index 5e2d72b..225fd79 100644 --- a/test/com/rpl/specter/core_test.cljx +++ b/test/com/rpl/specter/core_test.cljx @@ -1054,7 +1054,7 @@ (defspec transient-map-test (for-all+ - [m (gen/not-empty (gen/map gen/keyword gen/int)) + [m (limit-size 5 (gen/not-empty (gen/map gen/keyword gen/int))) new-key gen/keyword] (let [existing-key (first (keys m))] (every? identity From bc4d1d0051410369a8ef56a4f9c71797e7747621 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Mon, 6 Jun 2016 12:34:21 -0400 Subject: [PATCH 06/27] updated readme --- README.md | 103 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 6041d4f..8bce848 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,96 @@ # Specter [![Build Status](https://travis-ci.org/nathanmarz/specter.svg?branch=master)](http://travis-ci.org/nathanmarz/specter) -Specter is library for Clojure and ClojureScript for querying and manipulating arbitrarily complicated data structures very concisely. Its use cases range from transforming the values of a map to manipulating deeply nested data structures to performing sophisticated recursive tree transformations. Without Specter, writing these manipulations in Clojure manually is cumbersome and prone to error. +Specter is a Clojure and ClojureScript library that – because of its far-ranging applicability – is hard to describe in just a few sentences. At its core, Specter is a library for "composable navigation". Most commonly this is used for querying and transfoming nested data structures, but the concept generalizes far beyond that. Its effect is to enable you to write programs more rapidly and in a much more maintainable way. -Specter is fully extensible. At its core, it's just a protocol for how to navigate within a data structure. By extending this protocol, you can use Specter to navigate any data structure or object you have. +Here are three areas where Specter greatly improves Clojure programming: + +**Specter makes common tasks concise instead of cumbersome, and simple instead of complex** + +Example 1: Increment every value in a map + +```clojure +(def data {:a 1 :b 2 :c 3}) + +;; Manual Clojure: +(->> data (map (fn [[k v]] [k (inc v)])) (into {})) + +;; Specter: +(transform [ALL LAST] inc data) +``` + +Example 2: Increment every value nested within map of vector of maps + +```clojure +(def data {:a [{:aa 1 :bb 2} + {:cc 3}] + :b [{:dd 4}]}) + +;; Manual Clojure: +(defn map-vals [m afn] + (->> m (map (fn [[k v]] [k (afn v)])) (into {}))) +(map-vals data (fn [v] (mapv (fn [m] (map-vals m inc)) v))) + +;; Specter: +(def MAP-VALS (comp-paths ALL LAST)) +(transform [MAP-VALS ALL MAP-VALS] inc data) +``` + +**Specter is much faster than Clojure's limited built-in alternatives** + +Example 1: Specter's `select` is 27% faster than `get-in`: + +```clojure +(time + (dotimes [_ 10000000] + (get-in {:a {:b {:c 1}}} [:a :b :c]))) +"Elapsed time: 640.666 msecs" + +(time + (dotimes [_ 10000000] + (select [:a :b :c] {:a {:b {:c 1}}}))) +"Elapsed time: 470.167 msecs" +``` + +Example 2: Specter's `transform` is 6x faster than `update-in`: + +```clojure +(time + (dotimes [_ 10000000] + (update-in {:a {:b {:c 1}}} [:a :b :c] inc))) +"Elapsed time: 10662.014 msecs" + +(time + (dotimes [_ 10000000] + (transform [:a :b :c] inc {:a {:b {:c 1}}}))) +"Elapsed time: 1699.016 msecs" +``` + +**Specter makes sophisticated tasks – that are difficult to program manually – easy** + +Example 1: Reverse the order of even numbers in a tree (with order based on depth first search): + +```clojure +(transform (subselect (walker number?) even?) + reverse + [1 [[[2]] 3] 5 [6 [7 8]] 10]) +;; => [1 [[[10]] 3] 5 [8 [7 6]] 2] +``` + + +Example 2: Replace every continuous sequence of odd numbers with its sum: + +```clojure +(transform (continuous-subseqs odd?) + (fn [aseq] [(reduce + aseq)]) + [1 3 6 8 9 11 15 16] + ) +;; => [4 6 8 35 16] +``` + +This is just the tip of the iceberg. Because Specter is completely extensible, it can be used to navigate any data structure or object you have. All the navigators that come with Specter are built upon [very simple abstractions](https://github.com/nathanmarz/specter/blob/0.11.0/src/clj/com/rpl/specter/protocols.cljx). + +Even though Specter is so generic and flexible, its performance rivals hand-optimized code. Under the hood, Specter uses [advanced dynamic techniques](https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs) to strip away the overhead of composition. Additionally, the built-in navigators use the most efficient means possible of accessing data structures. For example, `ALL` uses `mapv` on vectors, `reduce-kv` on small maps, and `reduce-kv` in conjunction with transients on larger maps. You get the best of both worlds of elegance and performance. -Even though Specter is so generic and flexible, [its performance](https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs) rivals hand-optimized code. The only comparable functions in Clojure's core library are `get-in` and `update-in`. The equivalent Specter code is effectively identical (just different order of arguments), but Specter runs 30% faster than `get-in` and 5x faster than `update-in`. # Latest Version @@ -131,15 +217,6 @@ user> (transform [(srange 4 11) (filterer even?)] [0 1 2 3 10 5 8 7 6 9 4 11 12 13 14 15] ``` -Decrement every value in a map: - -```clojure -user> (transform [ALL LAST] - dec - {:a 1 :b 3}) -{:b 2 :a 0} -``` - Append [:c :d] to every subsequence that has at least two even numbers: ```clojure user> (setval [ALL @@ -226,7 +303,7 @@ The next examples demonstrate recursive navigation. Here's how to double all the ;; => [:a 1 [4 [[[3]]] :e] [8 5 [12 7]]] ``` -Here's how to reverse the positions of all even numbers in a tree (with order based on a depth first search). This example uses conditional navigation instead of protocol paths to do the walk: +Here's how to reverse the positions of all even numbers in a tree (with order based on a depth first search). This example uses conditional navigation instead of protocol paths to do the walk and is much more efficient than using `walker`: ```clojure (declarepath TreeValues) From 5d5ed2b8de21d2ebe4b7b1358c92bbf1848c2419 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Mon, 6 Jun 2016 12:55:10 -0400 Subject: [PATCH 07/27] improve readme --- README.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8bce848..35b27ae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Specter [![Build Status](https://travis-ci.org/nathanmarz/specter.svg?branch=master)](http://travis-ci.org/nathanmarz/specter) -Specter is a Clojure and ClojureScript library that – because of its far-ranging applicability – is hard to describe in just a few sentences. At its core, Specter is a library for "composable navigation". Most commonly this is used for querying and transfoming nested data structures, but the concept generalizes far beyond that. Its effect is to enable you to write programs more rapidly and in a much more maintainable way. +Specter is a Clojure and ClojureScript library that – because of its far-ranging applicability – is hard to describe in just a few sentences. At its core, Specter is a library for "composable navigation". Most commonly it is used for querying and transfoming nested data structures, but the concept generalizes far beyond that. Its effect is to enable you to write programs much more rapidly and in a much more maintainable way. Here are three areas where Specter greatly improves Clojure programming: @@ -18,7 +18,7 @@ Example 1: Increment every value in a map (transform [ALL LAST] inc data) ``` -Example 2: Increment every value nested within map of vector of maps +Example 2: Increment every even number nested within map of vector of maps ```clojure (def data {:a [{:aa 1 :bb 2} @@ -28,11 +28,18 @@ Example 2: Increment every value nested within map of vector of maps ;; Manual Clojure: (defn map-vals [m afn] (->> m (map (fn [[k v]] [k (afn v)])) (into {}))) -(map-vals data (fn [v] (mapv (fn [m] (map-vals m inc)) v))) +(map-vals data + (fn [v] + (mapv + (fn [m] + (map-vals + m + (fn [v] (if (even? v) (inc v) v)))) + v))) ;; Specter: (def MAP-VALS (comp-paths ALL LAST)) -(transform [MAP-VALS ALL MAP-VALS] inc data) +(transform [MAP-VALS ALL MAP-VALS even?] inc data) ``` **Specter is much faster than Clojure's limited built-in alternatives** @@ -119,18 +126,10 @@ You can also find help in the #specter channel on [Clojurians](http://clojurians # Examples -Increment all the values in a map: +Increment all the values in maps of maps: ```clojure user> (use 'com.rpl.specter) user> (use 'com.rpl.specter.macros) -user> (transform [ALL LAST] - inc - {:a 1 :b 2 :c 3}) -{:a 2, :b 3, :c 4} -``` - -Increment all the values in maps of maps: -```clojure user> (transform [ALL LAST ALL LAST] inc {:a {:aa 1} :b {:ba -1 :bb 2}}) @@ -178,7 +177,7 @@ user> (transform [(srange 1 4) ALL odd?] inc [0 1 2 3 4 5 6 7]) [0 2 2 4 4 5 6 7] ``` -Replace the subsequence from index 2 to 4 with [:a :b :c :d :e]: +Replace the subsequence from indices 2 to 4 with [:a :b :c :d :e]: ```clojure user> (setval (srange 2 4) [:a :b :c :d :e] [0 1 2 3 4 5 6 7 8 9]) @@ -192,7 +191,7 @@ user> (setval [ALL END] [:a :b] [[1] '(1 2) [:c]]) [[1 :a :b] (1 2 :a :b) [:c :a :b]] ``` -Get all the numbers out of a map, no matter how they're nested: +Get all the numbers out of a data structure, no matter how they're nested: ```clojure user> (select (walker number?) From 16063a671465947103ad71b4118b21a19136de26 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Mon, 6 Jun 2016 15:52:31 -0400 Subject: [PATCH 08/27] readme improvements --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 35b27ae..cc6fdf6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Specter [![Build Status](https://travis-ci.org/nathanmarz/specter.svg?branch=master)](http://travis-ci.org/nathanmarz/specter) -Specter is a Clojure and ClojureScript library that – because of its far-ranging applicability – is hard to describe in just a few sentences. At its core, Specter is a library for "composable navigation". Most commonly it is used for querying and transfoming nested data structures, but the concept generalizes far beyond that. Its effect is to enable you to write programs much more rapidly and in a much more maintainable way. +Specter is a Clojure and ClojureScript library that, because of its far-ranging applicability, is hard to describe in just a few sentences. At its core, Specter is a library for "composable navigation". Most commonly it is used for querying and transfoming nested data structures, but the concept generalizes far beyond that. Its effect is to enable you to write programs much more rapidly in a much more maintainable way. Here are three areas where Specter greatly improves Clojure programming: -**Specter makes common tasks concise instead of cumbersome, and simple instead of complex** +**Specter makes common tasks concise instead of cumbersome and simple instead of complex** Example 1: Increment every value in a map From 1efb3df8de71e09fb45eac3eb6bce9332d0bda72 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Mon, 6 Jun 2016 16:04:45 -0400 Subject: [PATCH 09/27] improve readme --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index cc6fdf6..1bcf6be 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Example 1: Increment every value in a map (->> data (map (fn [[k v]] [k (inc v)])) (into {})) ;; Specter: -(transform [ALL LAST] inc data) +(transform MAP-VALS inc data) ``` Example 2: Increment every even number nested within map of vector of maps @@ -38,7 +38,6 @@ Example 2: Increment every even number nested within map of vector of maps v))) ;; Specter: -(def MAP-VALS (comp-paths ALL LAST)) (transform [MAP-VALS ALL MAP-VALS even?] inc data) ``` From bafe10036fb3dae27e507db7cc2013bf33593d7a Mon Sep 17 00:00:00 2001 From: Alex Engelberg Date: Tue, 7 Jun 2016 23:16:23 -0700 Subject: [PATCH 10/27] Add benchmarks to test transient navigators --- scripts/benchmarks.clj | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/scripts/benchmarks.clj b/scripts/benchmarks.clj index 0516f52..61aa268 100644 --- a/scripts/benchmarks.clj +++ b/scripts/benchmarks.clj @@ -1,6 +1,7 @@ (ns com.rpl.specter.benchmarks (:use [com.rpl.specter] [com.rpl.specter macros] + [com.rpl.specter.transient] [com.rpl.specter.impl :only [benchmark]]) (:require [clojure.walk :as walk])) @@ -141,3 +142,46 @@ (tree-value-transform (fn [e] (if (even? e) (inc e) e)) data) )) +(run-benchmark "transient comparison: building up vectors" + 6 + 10000 + (reduce (fn [v i] (conj v i)) [] (range 1000)) + (reduce (fn [v i] (conj! v i)) (transient []) (range 1000)) + ;; uncomment this when END is fast + #_(reduce (fn [v i] (setval [END] [i] v)) [] (range 1000)) + (setval [END!] (range 1000) (transient [])) + (reduce (fn [v i] (setval [END!] [i] v)) (transient []) (range 1000))) + +(let [data (vec (range 1000)) + tdata (transient data)] + (run-benchmark "transient comparison: assoc'ing in vectors" + 6 + 500000 + (assoc data (rand-int 1000) 0) + (assoc! tdata (rand-int 1000) 0) + (setval [(keypath (rand-int 1000))] 0 data) + (setval [(keypath! (rand-int 1000))] 0 tdata))) + +(let [data (into {} (for [k (range 1000)] + [k (rand)])) + tdata (transient data)] + (run-benchmark "transient comparison: assoc'ing in maps" + 6 + 500000 + (assoc data (rand-int 1000) 0) + (assoc! tdata (rand-int 1000) 0) + (setval [(keypath (rand-int 1000))] 0 data) + (setval [(keypath! (rand-int 1000))] 0 tdata))) + +(defn modify-submap + [m] + (assoc m 0 1 458 89)) + +(let [data (into {} (for [k (range 1000)] + [k (rand)])) + tdata (transient data)] + (run-benchmark "transient comparison: submap" + 6 + 300000 + (transform [(submap [(rand-int 1000)])] modify-submap data) + (transform [(submap! [(rand-int 1000)])] modify-submap tdata))) From 2147584dca7c2f227cbd1ae4c157af402c8630c9 Mon Sep 17 00:00:00 2001 From: Alex Engelberg Date: Tue, 7 Jun 2016 23:16:54 -0700 Subject: [PATCH 11/27] Change reduce to reduce-kv --- src/clj/com/rpl/specter/transient.cljx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/clj/com/rpl/specter/transient.cljx b/src/clj/com/rpl/specter/transient.cljx index ef15d54..774e19f 100644 --- a/src/clj/com/rpl/specter/transient.cljx +++ b/src/clj/com/rpl/specter/transient.cljx @@ -86,6 +86,6 @@ (reduce (fn [m k] (dissoc! m k)) % m-keys) - (reduce (fn [m [k v]] - (assoc! m k v)) - % res))))) + (reduce-kv (fn [m k v] + (assoc! m k v)) + % res))))) From 82321d73707fbbd0421de1bd27fe87d0a0a1d95b Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 06:02:41 -0400 Subject: [PATCH 12/27] isolate desired operations to test in transient benchmarks and make the comparisons work on identical data, add transient namespace for doc generation --- project.clj | 3 ++- scripts/benchmarks.clj | 47 +++++++++++++++++++++--------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/project.clj b/project.clj index a646ba8..fd2966b 100644 --- a/project.clj +++ b/project.clj @@ -15,7 +15,8 @@ :namespaces [com.rpl.specter com.rpl.specter.macros com.rpl.specter.zipper - com.rpl.specter.protocols] + com.rpl.specter.protocols + com.rpl.specter.transient] :source-uri {#"target/classes" "https://github.com/nathanmarz/specter/tree/{version}/src/clj/{classpath}x#L{line}" #".*" "https://github.com/nathanmarz/specter/tree/{version}/src/clj/{classpath}#L{line}" diff --git a/scripts/benchmarks.clj b/scripts/benchmarks.clj index e806dc0..fafbbd1 100644 --- a/scripts/benchmarks.clj +++ b/scripts/benchmarks.clj @@ -154,36 +154,36 @@ (tree-value-transform (fn [e] (if (even? e) (inc e) e)) data) )) -(run-benchmark "transient comparison: building up vectors" - 6 - 10000 - (reduce (fn [v i] (conj v i)) [] (range 1000)) - (reduce (fn [v i] (conj! v i)) (transient []) (range 1000)) - ;; uncomment this when END is fast - #_(reduce (fn [v i] (setval [END] [i] v)) [] (range 1000)) - (setval [END!] (range 1000) (transient [])) - (reduce (fn [v i] (setval [END!] [i] v)) (transient []) (range 1000))) +(let [toappend (range 1000)] + (run-benchmark "transient comparison: building up vectors" + 10000 + (reduce (fn [v i] (conj v i)) [] toappend) + (reduce (fn [v i] (conj! v i)) (transient []) toappend) + (setval END toappend []) + (setval END! toappend (transient [])))) (let [data (vec (range 1000)) - tdata (transient data)] + tdata (transient data) + tdata2 (transient data) + idx 600] (run-benchmark "transient comparison: assoc'ing in vectors" - 6 500000 - (assoc data (rand-int 1000) 0) - (assoc! tdata (rand-int 1000) 0) - (setval [(keypath (rand-int 1000))] 0 data) - (setval [(keypath! (rand-int 1000))] 0 tdata))) + (assoc data idx 0) + (assoc! tdata idx 0) + (setval (keypath idx) 0 data) + (setval (keypath! idx) 0 tdata2))) (let [data (into {} (for [k (range 1000)] [k (rand)])) - tdata (transient data)] + tdata (transient data) + tdata2 (transient data) + idx 600] (run-benchmark "transient comparison: assoc'ing in maps" - 6 500000 - (assoc data (rand-int 1000) 0) - (assoc! tdata (rand-int 1000) 0) - (setval [(keypath (rand-int 1000))] 0 data) - (setval [(keypath! (rand-int 1000))] 0 tdata))) + (assoc data idx 0) + (assoc! tdata idx 0) + (setval (keypath idx) 0 data) + (setval (keypath! idx) 0 tdata2))) (defn modify-submap [m] @@ -193,7 +193,6 @@ [k (rand)])) tdata (transient data)] (run-benchmark "transient comparison: submap" - 6 300000 - (transform [(submap [(rand-int 1000)])] modify-submap data) - (transform [(submap! [(rand-int 1000)])] modify-submap tdata))) + (transform (submap [600 700]) modify-submap data) + (transform (submap! [600 700]) modify-submap tdata))) From 4cdc7a47a305d98aef2767df2cd5b6266fc4ec6b Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 06:09:24 -0400 Subject: [PATCH 13/27] updated changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 71152b0..00e2b1b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,6 @@ ## 0.11.1 (unreleased) * More efficient inline caching for Clojure version, benchmarks show inline caching within 5% of manually precompiled code for all cases +* Added navigators for transients in com.rpl.specter.transient namespace (thanks @aaengelberg) * Huge performance improvement for ALL transform on maps and vectors * Significant performance improvements for FIRST/LAST for vectors * Huge performance improvements for `if-path`, `cond-path`, `selected?`, and `not-selected?`, especially for condition path containing only static functions From 8231cd654f5fe619aa848ff5f2278feb6f833b56 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 06:11:21 -0400 Subject: [PATCH 14/27] benchmark keypath alongside direct keyword navigation --- scripts/benchmarks.clj | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/benchmarks.clj b/scripts/benchmarks.clj index fafbbd1..52b527f 100644 --- a/scripts/benchmarks.clj +++ b/scripts/benchmarks.clj @@ -55,6 +55,7 @@ (run-benchmark "get value in nested map" 10000000 (get-in data [:a :b :c]) (select [:a :b :c] data) + (select [(keypath :a) (keypath :b) (keypath :c)] data) (compiled-select p data) (-> data :a :b :c vector) ) From 50c176ba4825bf0370b7f2b67ca45eb314c7ef48 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 06:57:15 -0400 Subject: [PATCH 15/27] reinstate one-at-at-time vector append benchmark --- scripts/benchmarks.clj | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/benchmarks.clj b/scripts/benchmarks.clj index 52b527f..475413c 100644 --- a/scripts/benchmarks.clj +++ b/scripts/benchmarks.clj @@ -163,12 +163,21 @@ (setval END toappend []) (setval END! toappend (transient [])))) +(let [toappend (range 1000)] + (run-benchmark "transient comparison: building up vectors one at a time" + 10000 + (reduce (fn [v i] (conj v i)) [] toappend) + (reduce (fn [v i] (conj! v i)) (transient []) toappend) + (reduce (fn [v i] (setval END [i] v)) [] toappend) + (reduce (fn [v i] (setval END! [i] v)) (transient []) toappend) + )) + (let [data (vec (range 1000)) tdata (transient data) tdata2 (transient data) idx 600] (run-benchmark "transient comparison: assoc'ing in vectors" - 500000 + 2500000 (assoc data idx 0) (assoc! tdata idx 0) (setval (keypath idx) 0 data) @@ -180,7 +189,7 @@ tdata2 (transient data) idx 600] (run-benchmark "transient comparison: assoc'ing in maps" - 500000 + 2500000 (assoc data idx 0) (assoc! tdata idx 0) (setval (keypath idx) 0 data) From 3a9de5b70faacdc22791c5bd5b36be66119b774b Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 06:57:32 -0400 Subject: [PATCH 16/27] 0.11.1 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 9ba95d1..af88ba8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.11.1-SNAPSHOT +0.11.1 From 839d92da1460eaa6ee62e0b1f07024c6fda82d67 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 07:01:17 -0400 Subject: [PATCH 17/27] update changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 00e2b1b..09f712e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,4 @@ -## 0.11.1 (unreleased) +## 0.11.1 * More efficient inline caching for Clojure version, benchmarks show inline caching within 5% of manually precompiled code for all cases * Added navigators for transients in com.rpl.specter.transient namespace (thanks @aaengelberg) * Huge performance improvement for ALL transform on maps and vectors From 85f14e839869d1c57327cf3a13d4a74a14385b8e Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 07:06:04 -0400 Subject: [PATCH 18/27] update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6041d4f..1c6ecd0 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ Specter's API is contained in three files: - [macros.clj](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/macros.clj): This contains the core `select/transform/etc.` operations as well as macros for defining new navigators. - [specter.cljx](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter.cljx): This contains the build-in navigators and functional versions of `select/transform/etc.` -- [zippers.cljx](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/zipper.cljx): This integrates zipper-based navigation into Specter. +- [transient.cljx](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/transient.cljx): This contains navigators for transient collections. +- [zipper.cljx](https://github.com/nathanmarz/specter/blob/master/src/clj/com/rpl/specter/zipper.cljx): This integrates zipper-based navigation into Specter. # Questions? From 3d84d288d121342359ab212612a768aa28e13752 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 09:40:53 -0400 Subject: [PATCH 19/27] fix typo in readme --- CHANGES.md | 2 +- scripts/benchmarks.clj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 09f712e..f0cc5fb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,7 +11,7 @@ * Added :notpath metadata to signify pathedfn arguments that should be treated as regular arguments during inline factoring. If one of these arguments is not a static var reference or non-collection value, the path will not factor. * Bug fix: `transformed` transform-fn no longer factors into `pred` when an anonymous function during inline factoring * Bug fix: Fixed nil->val to not replace the val on `false` -* Bug fix: Eliminate reflection when using primitive paramaters in an inline cached path +* Bug fix: Eliminate reflection when using primitive parameters in an inline cached path ## 0.11.0 * New `path` macro does intelligent inline caching of the provided path. The path is factored into a static portion and into params which may change on each usage of the path (e.g. local parameters). The static part is factored and compiled on the first run-through, and then re-used for all subsequent invocations. As an example, `[ALL (keypath k)]` is factored into `[ALL keypath]`, which is compiled and cached, and `[k]`, which is provided on each execution. If it is not possible to precompile the path (e.g. [ALL some-local-variable]), nothing is cached and the path will be compiled on each run-through. diff --git a/scripts/benchmarks.clj b/scripts/benchmarks.clj index 475413c..4396209 100644 --- a/scripts/benchmarks.clj +++ b/scripts/benchmarks.clj @@ -31,7 +31,7 @@ (time-ms amt-per-iter afn)))) (defn compare-benchmark [amt-per-iter afn-map] - (let [results (transform [ALL LAST] + (let [results (transform MAP-VALS (fn [afn] (average-time-ms 8 amt-per-iter afn)) afn-map) From bd131246d8ff99e23fde7bb90e1c3936de89bcd6 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 09:58:22 -0400 Subject: [PATCH 20/27] improve readme --- README.md | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index b9a1d8c..5c1094e 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ # Specter [![Build Status](https://travis-ci.org/nathanmarz/specter.svg?branch=master)](http://travis-ci.org/nathanmarz/specter) -Specter is a Clojure and ClojureScript library that, because of its far-ranging applicability, is hard to describe in just a few sentences. At its core, Specter is a library for "composable navigation". Most commonly it is used for querying and transfoming nested data structures, but the concept generalizes far beyond that. Its effect is to enable you to write programs much more rapidly in a much more maintainable way. +Specter is a Clojure and ClojureScript library that, because of its far-ranging applicability, is hard to describe in just a few sentences. At its core, Specter is a library for "composable navigation". Most commonly it is used for querying and transforming nested data structures, but the concept generalizes far beyond that. Its effect is to enable you to write programs much more rapidly in a much more maintainable way. Here are three areas where Specter greatly improves Clojure programming: **Specter makes common tasks concise instead of cumbersome and simple instead of complex** -Example 1: Increment every value in a map +Example 1: Append a sequence of elements to a nested vector ```clojure -(def data {:a 1 :b 2 :c 3}) +(def data {:a [1 2 3]}) -;; Manual Clojure: -(->> data (map (fn [[k v]] [k (inc v)])) (into {})) +;; Manual Clojure +(update data :a (fn [v] (reduce conj v [4 5]))) -;; Specter: -(transform MAP-VALS inc data) +;; Specter +(setval [:a END] [4 5] data) ``` Example 2: Increment every even number nested within map of vector of maps @@ -93,7 +93,7 @@ Example 2: Replace every continuous sequence of odd numbers with its sum: ;; => [4 6 8 35 16] ``` -This is just the tip of the iceberg. Because Specter is completely extensible, it can be used to navigate any data structure or object you have. All the navigators that come with Specter are built upon [very simple abstractions](https://github.com/nathanmarz/specter/blob/0.11.0/src/clj/com/rpl/specter/protocols.cljx). +This is just the tip of the iceberg. Because Specter is completely extensible, it can be used to navigate any data structure or object you have. All the navigators that come with Specter are built upon [very simple abstractions](https://github.com/nathanmarz/specter/blob/0.11.1/src/clj/com/rpl/specter/protocols.cljx). Even though Specter is so generic and flexible, its performance rivals hand-optimized code. Under the hood, Specter uses [advanced dynamic techniques](https://github.com/nathanmarz/specter/wiki/Specter-0.11.0:-Performance-without-the-tradeoffs) to strip away the overhead of composition. Additionally, the built-in navigators use the most efficient means possible of accessing data structures. For example, `ALL` uses `mapv` on vectors, `reduce-kv` on small maps, and `reduce-kv` in conjunction with transients on larger maps. You get the best of both worlds of elegance and performance. @@ -130,15 +130,6 @@ Increment all the values in maps of maps: ```clojure user> (use 'com.rpl.specter) user> (use 'com.rpl.specter.macros) -user> (transform [ALL LAST ALL LAST] - inc - {:a {:aa 1} :b {:ba -1 :bb 2}}) -{:a {:aa 2}, :b {:ba 0, :bb 3}} -``` - -Do the previous example more concisely: -```clojure -user> (def MAP-VALS (comp-paths ALL LAST)) user> (transform [MAP-VALS MAP-VALS] inc {:a {:aa 1} :b {:ba -1 :bb 2}}) From 9b70adb07be211a2e01a855db5544fc7e8e34c33 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 09:59:23 -0400 Subject: [PATCH 21/27] formatting --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5c1094e..e057cbe 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Example 2: Increment every even number nested within map of vector of maps {:cc 3}] :b [{:dd 4}]}) -;; Manual Clojure: +;; Manual Clojure (defn map-vals [m afn] (->> m (map (fn [[k v]] [k (afn v)])) (into {}))) (map-vals data @@ -37,7 +37,7 @@ Example 2: Increment every even number nested within map of vector of maps (fn [v] (if (even? v) (inc v) v)))) v))) -;; Specter: +;; Specter (transform [MAP-VALS ALL MAP-VALS even?] inc data) ``` From 779fd72226fbe108bf2f54571b218f376530c679 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 10:09:39 -0400 Subject: [PATCH 22/27] formatting --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e057cbe..0823618 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Example 2: Increment every even number nested within map of vector of maps ;; Manual Clojure (defn map-vals [m afn] (->> m (map (fn [[k v]] [k (afn v)])) (into {}))) + (map-vals data (fn [v] (mapv From 294582e589d654b3c3da6e0b9cdcdf4eba8a2d64 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 10:24:04 -0400 Subject: [PATCH 23/27] fix credit on changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f0cc5fb..5e45150 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ ## 0.11.1 * More efficient inline caching for Clojure version, benchmarks show inline caching within 5% of manually precompiled code for all cases -* Added navigators for transients in com.rpl.specter.transient namespace (thanks @aaengelberg) +* Added navigators for transients in com.rpl.specter.transient namespace (thanks @aengelberg) * Huge performance improvement for ALL transform on maps and vectors * Significant performance improvements for FIRST/LAST for vectors * Huge performance improvements for `if-path`, `cond-path`, `selected?`, and `not-selected?`, especially for condition path containing only static functions From fbb7a1719732abfaaa9075ae14219f0d75667d4a Mon Sep 17 00:00:00 2001 From: Alex Engelberg Date: Wed, 8 Jun 2016 08:18:10 -0700 Subject: [PATCH 24/27] Add META navigator, test case, and benchmarks --- scripts/benchmarks.clj | 13 +++++++++++++ src/clj/com/rpl/specter.cljx | 9 +++++++++ test/com/rpl/specter/core_test.cljx | 9 +++++++++ 3 files changed, 31 insertions(+) diff --git a/scripts/benchmarks.clj b/scripts/benchmarks.clj index 4396209..f82bcd5 100644 --- a/scripts/benchmarks.clj +++ b/scripts/benchmarks.clj @@ -206,3 +206,16 @@ 300000 (transform (submap [600 700]) modify-submap data) (transform (submap! [600 700]) modify-submap tdata))) + +(let [data {:x 1} + meta-map {:my :metadata}] + (run-benchmark "set metadata" + 300000 + (with-meta data meta-map) + (setval META meta-map data))) + +(let [data (with-meta {:x 1} {:my :metadata})] + (run-benchmark "get metadata" + 300000 + (vector (meta data)) + (select META data))) diff --git a/src/clj/com/rpl/specter.cljx b/src/clj/com/rpl/specter.cljx index d55b8e9..6a3871c 100644 --- a/src/clj/com/rpl/specter.cljx +++ b/src/clj/com/rpl/specter.cljx @@ -498,6 +498,15 @@ NIL->VECTOR (nil->val [])) +(defnav ^{:doc "Navigates to the metadata of the structure, or nil if + the structure has no metadata or may not contain metadata."} + META + [] + (select* [this structure next-fn] + (next-fn (meta structure))) + (transform* [this structure next-fn] + (with-meta structure (next-fn (meta structure))))) + (defpathedfn ^{:doc "Adds the result of running select with the given path on the current value to the collected vals."} diff --git a/test/com/rpl/specter/core_test.cljx b/test/com/rpl/specter/core_test.cljx index 9aa1510..febde4e 100644 --- a/test/com/rpl/specter/core_test.cljx +++ b/test/com/rpl/specter/core_test.cljx @@ -1070,3 +1070,12 @@ (persistent! (s/transform* transient-path f (transient m)))) (= (s/select* path m) (s/select* transient-path (transient m))))))))) + +(defspec meta-test + (for-all+ + [v (gen/vector gen/int) + meta-map (limit-size 5 (gen/map gen/keyword gen/int))] + (= meta-map + (meta (setval s/META meta-map v)) + (first (select s/META (with-meta v meta-map))) + (first (select s/META (setval s/META meta-map v)))))) From 21e6289c64e7ee2e3adba467f70f26ded65d5c94 Mon Sep 17 00:00:00 2001 From: Alex Engelberg Date: Wed, 8 Jun 2016 08:25:03 -0700 Subject: [PATCH 25/27] Add benchmark to compare to vary-meta --- scripts/benchmarks.clj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/benchmarks.clj b/scripts/benchmarks.clj index f82bcd5..98cd979 100644 --- a/scripts/benchmarks.clj +++ b/scripts/benchmarks.clj @@ -219,3 +219,9 @@ 300000 (vector (meta data)) (select META data))) + +(let [data (with-meta {:x 1} {:my :metadata})] + (run-benchmark "vary metadata" + 300000 + (vary-meta data assoc :y 2) + (setval [META :y] 2 data))) From 7fa477f197ddb873d40b83dd070daf80cee2a585 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 13:35:29 -0400 Subject: [PATCH 26/27] update changelog --- CHANGES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 5e45150..80ae257 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,6 @@ +## 0.12.0-SNAPSHOT +* Added META navigator (thanks @aengelberg) + ## 0.11.1 * More efficient inline caching for Clojure version, benchmarks show inline caching within 5% of manually precompiled code for all cases * Added navigators for transients in com.rpl.specter.transient namespace (thanks @aengelberg) From 65da1056d5d45bf72a4dfea0da2bb97f27fd5766 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Wed, 8 Jun 2016 13:43:01 -0400 Subject: [PATCH 27/27] increase benchmark iterations --- scripts/benchmarks.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/benchmarks.clj b/scripts/benchmarks.clj index 98cd979..a1f5bf9 100644 --- a/scripts/benchmarks.clj +++ b/scripts/benchmarks.clj @@ -210,18 +210,18 @@ (let [data {:x 1} meta-map {:my :metadata}] (run-benchmark "set metadata" - 300000 + 3000000 (with-meta data meta-map) (setval META meta-map data))) (let [data (with-meta {:x 1} {:my :metadata})] (run-benchmark "get metadata" - 300000 + 30000000 (vector (meta data)) (select META data))) (let [data (with-meta {:x 1} {:my :metadata})] (run-benchmark "vary metadata" - 300000 + 3000000 (vary-meta data assoc :y 2) (setval [META :y] 2 data)))