Makes x/for to support kvs in and out, remove tag and map-kv (replaced by for)

This commit is contained in:
Christophe Grand 2016-06-01 10:28:08 +02:00
parent 07225a92a4
commit 219e7d2aac

View file

@ -6,25 +6,42 @@
(defmacro for (defmacro for
"Like clojure.core/for with the first expression being replaced by % (or _). Returns a transducer." "Like clojure.core/for with the first expression being replaced by % (or _). Returns a transducer."
[[binding %or_ & seq-exprs] body] [[binding %or_ & seq-exprs] body-expr]
(assert (and (symbol? %or_) (#{"%" "_"} (name %or_))) (assert (and (symbol? %or_) (#{"%" "_"} (name %or_)))
"The second element of the comprehension vector must be % or _.") "The second element of the comprehension vector must be % or _.")
(let [rf (gensym 'rf) (let [rf (gensym 'rf)
acc (gensym 'acc) acc (gensym 'acc)
body pair? #(and (vector? %) (= 2 (clj/count %)))
build (fn [init]
(clj/reduce (fn [body [expr binding]] (clj/reduce (fn [body [expr binding]]
(case binding (case binding
:let `(let ~expr ~body) :let `(let ~expr ~body)
:when `(if ~expr ~body ~acc) :when `(if ~expr ~body ~acc)
:while `(if ~expr ~body (reduced ~acc)) :while `(if ~expr ~body (reduced ~acc))
`(clj/reduce (fn [~acc ~binding] ~body) ~acc ~expr))) `(clj/reduce (fn [~acc ~binding] ~body) ~acc ~expr)))
`(~rf ~acc ~body) init (clj/partition 2 (rseq (vec seq-exprs)))))
(clj/partition 2 (rseq (vec seq-exprs))))] body (build `(~rf ~acc ~body-expr))
kvbody (when (pair? body-expr) (build `(~rf ~acc ~@body-expr)))
fnsym (if (and (pair? binding) (not (some keyword? binding)) (not (some #{"&"} (filter symbol? binding)))) `kvrf `fn)]
(if kvbody
`(fn [~rf] `(fn [~rf]
(fn (if-some [~rf (some-kvrf ~rf)]
(~fnsym
([] (~rf)) ([] (~rf))
([~acc] (~rf ~acc)) ([~acc] (~rf ~acc))
([~acc ~binding] ~body))))) ([~acc ~binding] ~kvbody)
~@(when (= fnsym `kvrf) [`([~acc ~@binding] ~kvbody)]))
(~fnsym
([] (~rf))
([~acc] (~rf ~acc))
([~acc ~binding] ~body)
~@(when (= fnsym `kvrf) [`([~acc ~@binding] ~body)]))))
`(fn [~rf]
(~fnsym
([] (~rf))
([~acc] (~rf ~acc))
([~acc ~binding] ~body)
~@(when (= fnsym `kvrf) [`([~acc ~@binding] ~body)]))))))
(defprotocol KvRfable "Protocol for reducing fns that takes key and val as separate arguments." (defprotocol KvRfable "Protocol for reducing fns that takes key and val as separate arguments."
(some-kvrf [f] "Returns a kvrf or nil")) (some-kvrf [f] "Returns a kvrf or nil"))
@ -45,6 +62,14 @@
~@(clj/for [[args & body] fn-bodies] ~@(clj/for [[args & body] fn-bodies]
`(invoke [~name ~@args] ~@body))))) `(invoke [~name ~@args] ~@body)))))
(defn ensure-kvrf [rf]
(or (some-kvrf rf)
(kvrf
([] (rf))
([acc] (rf acc))
([acc x] (rf acc x))
([acc k v] (rf acc (clojure.lang.MapEntry. k v))))))
(defn reduce (defn reduce
"A transducer that reduces a collection to a 1-item collection consisting of only the reduced result. "A transducer that reduces a collection to a 1-item collection consisting of only the reduced result.
Unlike reduce but like transduce it does call the completing arity (1) of the reducing fn." Unlike reduce but like transduce it does call the completing arity (1) of the reducing fn."
@ -151,9 +176,8 @@
(let [make-rf (cond (let [make-rf (cond
(nil? pair) (constantly (multiplexable rf)) (nil? pair) (constantly (multiplexable rf))
(= ::default pair) (= ::default pair)
(if-some [rf (some-kvrf rf)] (let [rf (ensure-kvrf rf)]
(fn [k] (fn ([acc] acc) ([acc v] (rf acc k v)))) (fn [k] (fn ([acc] acc) ([acc v] (rf acc k v)))))
(fn [k] (fn ([acc] acc) ([acc v] (rf acc (vector k v))))))
:else (fn [k] (fn ([acc] acc) ([acc v] (rf acc (pair k v)))))) :else (fn [k] (fn ([acc] acc) ([acc v] (rf acc (pair k v))))))
m (volatile! (transient {}))] m (volatile! (transient {}))]
(if (and (nil? kfn) (nil? vfn)) (if (and (nil? kfn) (nil? vfn))
@ -290,7 +314,14 @@
n is the integral number of steps by which the window slides. With a 1-hour window, 4 means that the window slides every 15 minutes. n is the integral number of steps by which the window slides. With a 1-hour window, 4 means that the window slides every 15 minutes.
f and invf work like in #'window." f and invf work like in #'window."
[timef n f invf] ([timef n f]
(window-by-time timef n
(fn
([] clojure.lang.PersistentQueue/EMPTY)
([q] (f (clj/reduce f (f) q)))
([q x] (conj q x)))
(fn [q _] (pop q))))
([timef n f invf]
(let [timef (fn [x] (long (Math/floor (* n (timef x)))))] (let [timef (fn [x] (long (Math/floor (* n (timef x)))))]
(fn [rf] (fn [rf]
(let [dq (java.util.ArrayDeque.) (let [dq (java.util.ArrayDeque.)
@ -331,7 +362,7 @@
acc (flush! acc prev-limit limit)] acc (flush! acc prev-limit limit)]
(when-not (reduced? acc) (when-not (reduced? acc)
(vswap! vwacc f x)) (vswap! vwacc f x))
acc))))))) acc))))))))
(defn count ([] 0) ([n] n) ([n _] (inc n))) (defn count ([] 0) ([n] n) ([n _] (inc n)))
@ -386,33 +417,3 @@
(fn (fn
))) )))
) )
(defn tag
"Like (map #(vector tag %)) but potentially more efficient."
[tag]
(fn [rf]
(if-some [rf (some-kvrf rf)]
(fn
([] (rf))
([acc] (rf acc))
([acc v] (rf acc tag v)))
(fn
([] (rf))
([acc] (rf acc))
([acc v] (rf acc [tag v]))))))
(defn map-kv
"Like (map (fn [[k v]] [(kf k v) (vf k v)])) but potentially more efficient."
[kf vf]
(fn [rf]
(if-some [rf (some-kvrf rf)]
(kvrf
([] (rf))
([acc] (rf acc))
([acc [k v]] (rf acc (kf k v) (vf k v)))
([acc k v] (rf acc (kf k v) (vf k v))))
(kvrf
([] (rf))
([acc] (rf acc))
([acc [k v]] (rf acc [(kf k v) (vf k v)]))
([acc k v] (rf acc [(kf k v) (vf k v)]))))))