Merge pull request #107 from aengelberg/transient-navigators

Transient navigators
This commit is contained in:
Nathan Marz 2016-06-08 05:48:51 -04:00
commit 49957f2536
4 changed files with 188 additions and 0 deletions

View file

@ -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)))

View file

@ -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

View 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)))))

View file

@ -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)))))))))