This commit is contained in:
Christophe Grand 2015-09-03 12:39:22 +02:00
commit 09953646eb
3 changed files with 139 additions and 0 deletions

44
README.md Normal file
View file

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

6
project.clj Normal file
View file

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

89
src/net/cgrand/xforms.clj Normal file
View file

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