Merge pull request #107 from aengelberg/transient-navigators
Transient navigators
This commit is contained in:
commit
49957f2536
4 changed files with 188 additions and 0 deletions
|
|
@ -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]))
|
||||
|
||||
|
|
@ -153,3 +154,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)))
|
||||
|
|
|
|||
|
|
@ -497,6 +497,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]
|
||||
|
|
@ -536,6 +544,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))
|
||||
|
|
@ -831,6 +842,16 @@
|
|||
(next-fn structure)
|
||||
))
|
||||
|
||||
(deftype TransientEndNavigator [])
|
||||
|
||||
(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))))
|
||||
|
||||
(defn extract-basic-filter-fn [path]
|
||||
(cond (fn? path)
|
||||
path
|
||||
|
|
|
|||
91
src/clj/com/rpl/specter/transient.cljx
Normal file
91
src/clj/com/rpl/specter/transient.cljx
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
(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 selected?]]))
|
||||
|
||||
(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)]))
|
||||
|
||||
(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."
|
||||
(i/->PosNavigator t-get-first t-update-first))
|
||||
|
||||
(def LAST!
|
||||
"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."
|
||||
[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))))))
|
||||
|
||||
#+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!
|
||||
[m-keys]
|
||||
(select* [this structure next-fn]
|
||||
(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-kv (fn [m k v]
|
||||
(assoc! m k v))
|
||||
% res)))))
|
||||
|
|
@ -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:
|
||||
|
|
@ -1038,3 +1039,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 (limit-size 5 (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)))))))))
|
||||
|
|
|
|||
Loading…
Reference in a new issue