2015-09-03 10:39:22 +00:00
( ns net.cgrand.xforms
"Extra transducers for Clojure"
{ :author "Christophe Grand" }
2015-09-03 12:25:19 +00:00
( :refer-clojure :exclude [ reduce for partition str juxt ] )
2015-09-03 10:39:22 +00:00
( :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
2015-09-03 12:25:19 +00:00
" 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. "
2015-09-03 10:39:22 +00:00
( [ 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
2015-09-03 10:54:47 +00:00
" Returns a transducer which partitions items according to kfn.
It applies the transform specified by xform to each partition.
Partitions contain the \" value part \" ( as returned by vfn ) of each item.
The resulting transformed items are wrapped back into a \" pair \" using the pair function.
2015-09-03 11:18:48 +00:00
Default values for kfn , vfn and pair are first , second ( or identity if kfn is specified ) and vector. "
2015-09-03 10:39:22 +00:00
( [ 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 ) ) ) ) ) ) ) )
2015-09-03 12:25:19 +00:00
( defn avg
"Reducing fn to compute the arithmetic mean."
( [ ]
( let [ count ( volatile! 0 )
sum ( volatile! 0 ) ]
( fn secret-container
( [ ] ( when ( pos? @ count ) ( / @ sum @ count ) ) )
( [ n ]
( vswap! count inc )
( vswap! sum + n )
secret-container ) ) ) )
( [ acc ] ( acc ) )
( [ acc x ] ( acc x ) ) )
2015-09-03 10:39:22 +00:00
2015-09-03 12:25:19 +00:00
( defn juxt
" Returns a reducing fn which compute all rfns at once and whose final return
value is a vector of the final return values of each rfns. "
[ & rfns ]
( let [ rfns ( vec rfns ) ]
( fn
( [ ] ( mapv # ( vector % ( volatile! ( % ) ) ) rfns ) )
( [ acc ] ( mapv ( fn [ [ rf vacc ] ] ( rf ( unreduced @ vacc ) ) ) acc ) )
( [ acc x ]
( let [ some-unreduced ( reduce ( fn [ some-unreduced [ rf vacc ] ]
( when-not ( reduced? @ vacc ) ( vswap! vacc rf x ) true ) )
false acc ) ]
( if some-unreduced acc ( reduced acc ) ) ) ) ) ) )
2015-09-03 10:39:22 +00:00