commit 09953646eb8e112fece131957c9730c62ddde019 Author: Christophe Grand Date: Thu Sep 3 12:39:22 2015 +0200 init diff --git a/README.md b/README.md new file mode 100644 index 0000000..1446831 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# xforms + +More transducers and reduciing functions for Clojure! + +## Usage + +```clj +=> (require '[net.cgrand.xforms :as x]) +``` + +`str` and `str!` are two reducing functions to build Strings and StringBuilders in linear time. + +```clj +=> (quick-bench (reduce str (range 256))) + Execution time mean : 58,714946 µs +=> (quick-bench (reduce x/str (range 256))) + Execution time mean : 11,609631 µs +``` + +`by-key` and `reduce` are two new transducers. Here is an example usage: + +```clj +;; reimplementing group-by +(defn my-group-by [kfn coll] + (into {} (by-key kfn (reduce conj)) coll)) + +;; let's go transient! +(defn my-group-by [kfn coll] + (into {} (by-key kfn (reduce (completing conj! persistent!))) coll)) + +=> (quick-bench (group-by odd? (range 256))) + Execution time mean : 29,356531 µs +=> (quick-bench (my-group-by odd? (range 256))) + Execution time mean : 20,604297 µs +``` + + + +## License + +Copyright © 2015 Christophe Grand + +Distributed under the Eclipse Public License either version 1.0 or (at +your option) any later version. diff --git a/project.clj b/project.clj new file mode 100644 index 0000000..0fbd6c3 --- /dev/null +++ b/project.clj @@ -0,0 +1,6 @@ +(defproject net.cgrand/xforms "0.1.0-SNAPSHOT" + :description "Extra transducers for Clojure" + #_#_:url "http://example.com/FIXME" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :dependencies [[org.clojure/clojure "1.7.0"]]) diff --git a/src/net/cgrand/xforms.clj b/src/net/cgrand/xforms.clj new file mode 100644 index 0000000..f0bbe4a --- /dev/null +++ b/src/net/cgrand/xforms.clj @@ -0,0 +1,89 @@ +(ns net.cgrand.xforms + "Extra transducers for Clojure" + {:author "Christophe Grand"} + (:refer-clojure :exclude [reduce for partition str]) + (:require [clojure.core :as clj])) + +(defmacro for + "Like clojure.core/for with the first expression being replaced by % (or _). Returns a transducer." + [[binding %or_ & seq-exprs] body] + (assert (and (symbol? %or_) (#{"%" "_"} (name %or_))) + "The second element of the comprehension vector must be % or _.") + (let [rf (gensym 'rf) + acc (gensym 'acc) + body + (clj/reduce (fn [body [expr binding]] + (case binding + :let `(let ~expr ~body) + :when `(if ~expr ~body ~acc) + :while `(if ~expr ~body (reduced ~acc)) + `(reduce (fn [~acc ~binding] ~body) ~acc ~expr))) + `(~rf ~acc ~body) + (clj/partition 2 (rseq (vec seq-exprs))))] + `(fn [~rf] + (fn + ([] (~rf)) + ([~acc] (~rf ~acc)) + ([~acc ~binding] ~body))))) + +(defn reduce + "A transducer that reduces a collection to a 1-item collection consisting of only the reduced result." + ([f] + (fn [rf] + (let [vacc (volatile! (f))] + (fn + ([] (rf)) + ([acc] (rf (rf acc (f (unreduced @vacc))))) + ([acc x] + (if (reduced? (vswap! vacc f x)) + (reduced acc) + acc)))))) + ([f init] + (reduce (fn ([] init) ([acc] (f acc)) ([acc x] (f acc x)))))) + +(defmacro ^:private or-instance? [class x y] + (let [xsym (gensym 'x_)] + `(let [~xsym ~x] + (if (instance? ~class ~xsym) ~(with-meta xsym {:tag class}) ~y)))) + +(defn str! + "Like xforms/str but returns a StringBuilder." + ([] (StringBuilder.)) + ([sb] (or-instance? StringBuilder sb (StringBuilder. (clj/str sb)))) ; the instance? checks are for compatibility with str in case of seeded reduce/transduce. + ([sb x] (.append (or-instance? StringBuilder sb (StringBuilder. (clj/str sb))) x))) + +(def str + "Reducing function to build strings in linear time. Acts as replacement for clojure.core/str in a reduce/transduce call." + (completing str! clj/str)) + +;; for both map entries and vectors +(defn- key' [kv] (nth kv 0)) +(defn- val' [kv] (nth kv 1)) + +(defn- noprf "The noop reducing function" ([acc] acc) ([acc _] acc)) + +(defn by-key + "Returns a tranducer that applies the transducer xform independently for items partitioned by kfn. + (This docstring is a hard nut to crack.)" + ([xform] (by-key key' val' vector xform)) + ([kfn xform] (by-key kfn identity vector xform)) + ([kfn vfn xform] (by-key kfn vfn vector xform)) + ([kfn vfn pair xform] + (fn [rf] + (let [make-rf (if pair + (fn [k] (fn ([acc] (rf acc)) ([acc v] (rf acc (pair k v))))) + (constantly rf)) + m (volatile! (transient {}))] + (fn self + ([] (rf)) + ([acc] (clj/reduce (fn [acc krf] (krf acc)) acc (vals (persistent! @m)))) + ([acc x] + (let [k (kfn x) + krf (or (get @m k) (doto (xform (make-rf x)) (->> (vswap! m assoc! k)))) + acc (krf acc (vfn x))] + (when (reduced? acc) + (vswap! m assoc! k noprf)) + (unreduced acc)))))))) + + +