paramspath working with composition, measured about 14% slower than compiled path without params and 15x faster than select with on the fly compilation

This commit is contained in:
Nathan Marz 2015-09-10 13:56:33 -04:00
parent 6e3f79dd53
commit d8feed2ca1
2 changed files with 188 additions and 21 deletions

View file

@ -108,6 +108,55 @@
[selector transform-fn structure & {:keys [merge-fn] :or {merge-fn concat}}] [selector transform-fn structure & {:keys [merge-fn] :or {merge-fn concat}}]
(compiled-replace-in (i/comp-paths* selector) transform-fn structure :merge-fn merge-fn)) (compiled-replace-in (i/comp-paths* selector) transform-fn structure :merge-fn merge-fn))
(def bind-params i/bind-params)
;; paramspath* [bindings num-params-sym [impl1 impl2]]
(defmacro paramspath [params & impls]
(let [num-params (count params)
retrieve-params (->> params
(map-indexed
(fn [i p]
[p `(aget ~i/PARAMS-SYM
(+ ~i/PARAMS-IDX-SYM ~i))]
))
(apply concat))]
(i/paramspath* retrieve-params num-params impls)
))
(defmacro defparamspath [name & body]
`(def ~name (paramspath ~@body)))
(defmacro params-paramspath [bindings & impls]
(let [quoted-bindings (->> bindings
(partition 2)
(map (fn [[sym path-sym]]
[`(quote ~sym) `(quote ~(gensym "path")) path-sym]
)))]
`(i/params-paramspath* ~quoted-bindings (quote ~impls))
))
(defn filterer [& path]
(let [path (i/comp-paths* path)]
(params-paramspath [late path]
(select* [this structure next-fn]
;; same code
)
(transform* [this structure next-fn]
;; same code
))))
;;TODO: figure out how to express higher order selectors like filterer, selected?, cond-path
;; - if keep params-idx in compiledpath too, then:
;; - needs to emit paramsneeded if it needs params
;; - at runtime, it converts internal selector into CompiledPath with
;; the current params/params-idx
;;TODO: figure out how to express srange in terms of srange-dynamic
;; - will need selector and transformer to call into shared functions
;;TODO: get rid of KeyPath
;; Built-in pathing and context operations ;; Built-in pathing and context operations
(def ALL (i/->AllStructurePath)) (def ALL (i/->AllStructurePath))
@ -118,8 +167,10 @@
(def FIRST (i/->PosStructurePath first i/set-first)) (def FIRST (i/->PosStructurePath first i/set-first))
;;TODO: should be parameterized
(defn srange-dynamic [start-fn end-fn] (i/->SRangePath start-fn end-fn)) (defn srange-dynamic [start-fn end-fn] (i/->SRangePath start-fn end-fn))
;;TODO: should be parameterized
(defn srange [start end] (srange-dynamic (fn [_] start) (fn [_] end))) (defn srange [start end] (srange-dynamic (fn [_] start) (fn [_] end)))
(def BEGINNING (srange 0 0)) (def BEGINNING (srange 0 0))
@ -130,9 +181,33 @@
(defn codewalker [afn] (i/->CodeWalkerStructurePath afn)) (defn codewalker [afn] (i/->CodeWalkerStructurePath afn))
;;TODO: needs to parameterize if necessary according to its path
;; same for selected?, not-selected?, transformed, collect, collect-one,
;; cond-path, multi-path
;; TODO: but should only become a late bound object if its internal path
;; is parameterized
;; want an interface that gives regular structure path interface but
;; creates the right thing
; (higherorderparamspath [late1 path1
; late2 path2
; late3 path3]
; (select* [this structure next-fn]
; (compiled-select late1 ...)
; ;;TODO: if its multiple paths... where to do the index manipulation...?
; ;;could take in another arg of "latebound paths" that can then be used internally...
; ;; but if nothing was higher order, then its just direct
; ;; this never directly accesses params
; ))
(defn filterer [& path] (i/->FilterStructurePath (i/comp-paths* path))) (defn filterer [& path] (i/->FilterStructurePath (i/comp-paths* path)))
(defn keypath [akey] (i/->KeyPath akey)) (defparamspath 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)))
))
(defn view [afn] (i/->ViewPath afn)) (defn view [afn] (i/->ViewPath afn))

View file

@ -9,6 +9,13 @@
[clojure.string :as s]) [clojure.string :as s])
) )
(def ^:dynamic *tmp-closure*)
(defn closed-code [closure body]
(let [lv (mapcat #(vector % `(*tmp-closure* '~%))
(keys closure))]
(binding [*tmp-closure* closure]
(eval `(let [~@lv] ~body)))))
(defprotocol PathComposer (defprotocol PathComposer
(comp-paths* [paths])) (comp-paths* [paths]))
@ -38,13 +45,13 @@
(def RichPathExecutor (def RichPathExecutor
(->ExecutorFunctions (->ExecutorFunctions
:richpath :richpath
(fn [params selector structure] (fn [params params-idx selector structure]
(selector params 0 [] structure (selector params params-idx [] structure
(fn [params params-idx vals structure] (fn [_ _ vals structure]
(if-not (empty? vals) [(conj vals structure)] [structure])))) (if-not (empty? vals) [(conj vals structure)] [structure]))))
(fn [params transformer transform-fn structure] (fn [params params-idx transformer transform-fn structure]
(transformer params 0 [] structure (transformer params params-idx [] structure
(fn [params params-idx vals structure] (fn [_ _ vals structure]
(if (empty? vals) (if (empty? vals)
(transform-fn structure) (transform-fn structure)
(apply transform-fn (conj vals structure)))))) (apply transform-fn (conj vals structure))))))
@ -53,19 +60,30 @@
(def StructurePathExecutor (def StructurePathExecutor
(->ExecutorFunctions (->ExecutorFunctions
:spath :spath
(fn [params selector structure] (fn [params params-idx selector structure]
(selector structure (fn [structure] [structure]))) (selector structure (fn [structure] [structure])))
(fn [params transformer transform-fn structure] (fn [params params-idx transformer transform-fn structure]
(transformer structure transform-fn)) (transformer structure transform-fn))
)) ))
(defrecord TransformFunctions [executors selector transformer]) (defrecord TransformFunctions [executors selector transformer])
(defrecord CompiledPath [transform-fns params]) (defrecord CompiledPath [transform-fns params params-idx])
(defn no-params-compiled-path [transform-fns]
(->CompiledPath transform-fns nil 0))
;;TODO: this must implement IFn so it can be transformed to CompiledPath ;;TODO: this must implement IFn so it can be transformed to CompiledPath
;; (just calls bind-params)
(defrecord ParamsNeededPath [transform-fns num-needed-params]) (defrecord ParamsNeededPath [transform-fns num-needed-params])
(defn bind-params [^ParamsNeededPath params-needed-path params idx]
(->CompiledPath
(.-transform-fns params-needed-path)
params
idx))
(defn- seq-contains? [aseq val] (defn- seq-contains? [aseq val]
(->> aseq (->> aseq
(filter (partial = val)) (filter (partial = val))
@ -123,9 +141,8 @@
afn (fn [params params-idx vals structure next-fn] afn (fn [params params-idx vals structure next-fn]
(next-fn params params-idx (conj vals (cfn this structure)) structure) (next-fn params params-idx (conj vals (cfn this structure)) structure)
)] )]
(->CompiledPath (no-params-compiled-path
(->TransformFunctions RichPathExecutor afn afn) (->TransformFunctions RichPathExecutor afn afn)
nil
))) )))
@ -133,28 +150,26 @@
(let [pimpl (structure-path-impl this) (let [pimpl (structure-path-impl this)
selector (:select* pimpl) selector (:select* pimpl)
transformer (:transform* pimpl)] transformer (:transform* pimpl)]
(->CompiledPath (no-params-compiled-path
(->TransformFunctions (->TransformFunctions
StructurePathExecutor StructurePathExecutor
(fn [structure next-fn] (fn [structure next-fn]
(selector this structure next-fn)) (selector this structure next-fn))
(fn [structure next-fn] (fn [structure next-fn]
(transformer this structure next-fn))) (transformer this structure next-fn)))
nil
))) )))
(defn coerce-structure-path-rich [this] (defn coerce-structure-path-rich [this]
(let [pimpl (structure-path-impl this) (let [pimpl (structure-path-impl this)
selector (:select* pimpl) selector (:select* pimpl)
transformer (:transform* pimpl)] transformer (:transform* pimpl)]
(->CompiledPath (no-params-compiled-path
(->TransformFunctions (->TransformFunctions
RichPathExecutor RichPathExecutor
(fn [params params-idx vals structure next-fn] (fn [params params-idx vals structure next-fn]
(selector this structure (fn [structure] (next-fn params params-idx vals structure)))) (selector this structure (fn [structure] (next-fn params params-idx vals structure))))
(fn [params params-idx vals structure next-fn] (fn [params params-idx vals structure next-fn]
(transformer this structure (fn [structure] (next-fn params params-idx vals structure))))) (transformer this structure (fn [structure] (next-fn params params-idx vals structure)))))
nil
))) )))
(defn structure-path? [obj] (defn structure-path? [obj]
@ -264,8 +279,10 @@
(transformer params 0 vals structure (transformer params 0 vals structure
(fn [_ _ vals-next structure-next] (fn [_ _ vals-next structure-next]
(next-fn x-params params-idx vals-next structure-next) (next-fn x-params params-idx vals-next structure-next)
))) ))))
)))))) params
0
)))))
(extend-protocol PathComposer (extend-protocol PathComposer
nil nil
@ -294,7 +311,7 @@
) )
needs-params-paths (filter #(instance? ParamsNeededPath %) coerced)] needs-params-paths (filter #(instance? ParamsNeededPath %) coerced)]
(if (empty? needs-params-paths) (if (empty? needs-params-paths)
(->CompiledPath result-tfn nil) (no-params-compiled-path result-tfn)
(->ParamsNeededPath (->ParamsNeededPath
(coerce-tfns-rich result-tfn) (coerce-tfns-rich result-tfn)
(->> needs-params-paths (->> needs-params-paths
@ -303,6 +320,81 @@
)) ))
)))) ))))
;; parameterized path helpers
(defn determine-params-impls [[name1 & impl1] [name2 & impl2]]
(if (= name1 'select*)
[impl1 impl2]
[impl2 impl1]))
(def PARAMS-SYM (vary-meta (gensym "params") assoc :tag 'objects))
(def PARAMS-IDX-SYM (gensym "params-idx"))
(defn paramspath* [bindings num-params [impl1 impl2]]
(let [[[[_ s-structure-sym s-next-fn-sym] & select-body]
[[_ t-structure-sym t-next-fn-sym] & transform-body]]
(determine-params-impls impl1 impl2)
params-sym (gensym "params")
params-idx-sym (gensym "params-idx")]
`(->ParamsNeededPath
(->TransformFunctions
RichPathExecutor
(fn [~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~s-structure-sym next-fn#]
(let [~s-next-fn-sym (fn [structure#]
(next-fn#
~PARAMS-SYM
(+ ~PARAMS-IDX-SYM ~num-params)
vals#
structure#))
~@bindings]
~@select-body
))
(fn [~PARAMS-SYM ~PARAMS-IDX-SYM vals# ~t-structure-sym next-fn#]
(let [~t-next-fn-sym (fn [structure#]
(next-fn#
~PARAMS-SYM
(+ ~PARAMS-IDX-SYM ~num-params)
vals#
structure#))
~@bindings]
~@transform-body
)))
~num-params
)))
(defn num-needed-params [path]
(if (instance? CompiledPath path)
0
(:num-needed-params path)))
(defn params-paramspath* [bindings impls]
(let [num-params-seq (->> bindings
(map last)
(map num-needed-params)
(reductions +)
(cons 0))
num-params (last num-params-seq)
closure (->> bindings (map rest) (into {}))
make-paths (->> bindings
(map (fn [offset [late-sym path-sym path]]
[late-sym
(if (instance? CompiledPath path)
path-sym
`(bind-params ~path-sym ~PARAMS-SYM (+ ~PARAMS-IDX-SYM ~offset))
)
])
num-params-seq)
(apply concat))
_ (println "CLOSURE:" closure)
params-needed-path (closed-code closure (paramspath* make-paths num-params impls))]
(if (= num-params 0)
(bind-params params-needed-path nil 0)
params-needed-path)
))
;; cell implementation idea taken from prismatic schema library ;; cell implementation idea taken from prismatic schema library
(defprotocol PMutableCell (defprotocol PMutableCell
#?(:clj (get_cell [cell])) #?(:clj (get_cell [cell]))
@ -382,14 +474,14 @@
[^com.rpl.specter.impl.CompiledPath path structure] [^com.rpl.specter.impl.CompiledPath path structure]
(let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path) (let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)
^com.rpl.specter.impl.ExecutorFunctions ex (.-executors tfns)] ^com.rpl.specter.impl.ExecutorFunctions ex (.-executors tfns)]
((.-select-executor ex) (.-params path) (.-selector tfns) structure) ((.-select-executor ex) (.-params path) (.-params-idx path) (.-selector tfns) structure)
)) ))
(defn compiled-transform* (defn compiled-transform*
[^com.rpl.specter.impl.CompiledPath path transform-fn structure] [^com.rpl.specter.impl.CompiledPath path transform-fn structure]
(let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path) (let [^com.rpl.specter.impl.TransformFunctions tfns (.-transform-fns path)
^com.rpl.specter.impl.ExecutorFunctions ex (.-executors tfns)] ^com.rpl.specter.impl.ExecutorFunctions ex (.-executors tfns)]
((.-transform-executor ex) (.-params path) (.-transformer tfns) transform-fn structure) ((.-transform-executor ex) (.-params path) (.-params-idx path) (.-transformer tfns) transform-fn structure)
)) ))
(defn selected?* (defn selected?*