かなり久々のClojureネタです。
以前から、試してみたいと思いつつ放っておいた、ClojureのReducersについて。
APIドキュメントは、このあたりから。
http://clojure.github.io/clojure/clojure.core-api.html#clojure.core.reducers/-%3ECat
Clojureのオフィシャルサイトに載ってるブログエントリ。
Reducers - A Library and Model for Collection Processing
http://clojure.com/blog/2012/05/08/reducers-a-library-and-model-for-collection-processing.html
Anatomy of a Reducer
http://clojure.com/blog/2012/05/15/anatomy-of-reducer.html
日本語情報としては、このあたりを参考に。
1.5 の clojure.core.reducers って速くなっただけ?
http://sunday-programming.hatenablog.com/entry/2013/03/13/215645
Reducers は並列化のためだけのライブラリではない
http://tnoda-clojure.tumblr.com/post/60197800824/reducers
mapやreduce関数などをfork/joinフレームワークを使って並列化してくれるもののようですが、その他にもちょっと変わったところがあるようで。
では、ちょっと試していってみます。
まずは、require。
(require '[clojure.core.reducers :as r])
mapとreduce。標準のものと比べながら。
;; clojure.core.reducers版 (assert (= (r/reduce + (r/map #(* % 2) [1 2 3])) 12)) ;; 標準ライブラリ版 (assert (= (reduce + (map #(* % 2) [1 2 3])) 12))
これだけ見ると、変わったように見えません。
が、mapの結果だけを取り出そうとすると、ちょっと違いがあります。
;; clojure.core.reducers版 (assert (= (into [] (r/map #(* % 2) [1 2 3])) [2 4 6])) ;; 標準ライブラリ版 (assert (= (map #(* % 2) [1 2 3]) [2 4 6]))
Reducersのmap関数などは、シーケンスが返ってくるのではなくてfolderというものが返ってくるようです。なので、シーケンスとして取得したい場合は明示的にintoなどでベクタなどに放り込む必要があるとのこと。
セットやシーケンスでもよさそう。
user=> (into [] (r/map inc [1 1 1 2])) [2 2 2 3] user=> (into #{} (r/map inc [1 1 1 2])) #{2 3} user=> (into () (r/map inc [1 1 1 2])) (3 2 2 2)
あとは、いくつかピックアップで。
filter
;; filter (assert (= (into [] (r/filter odd? [1 2 3 4 5])) [1 3 5])) (assert (= (->> [1 2 3 4 5] (r/map #(+ % 3)) ;; [3 5 6 7 8] (r/filter odd?) ;; [5 7] (r/reduce +)) ;; 12 12))
flattern
;; flattern (assert (= (into [] (r/flatten [[1 2 3] [4 5 6]])) [1 2 3 4 5 6]))
fold
;; fold (assert (= (r/fold + [1 2 3]) 6))
foldcat
;; foldcat (assert (= (r/foldcat [1 2 3 4 5 6]) [1 2 3 4 5 6]))
mapcat
;; mapcat (assert (= (into [] (r/mapcat #(map inc %) [[1 2 3] [4 5 6]])) [2 3 4 5 6 7]))
append!
;; append! ;; JavaのCollection.addを呼んでるだけ? (def col (doto (java.util.ArrayList.) (.add 1) (.add 2) (.add 3))) ;; 上記の初期化コードとほとんど変わらないのでは… (assert (= (r/append! col 4) [1 2 3 4]))
cat
;; cat (assert (= (r/cat [] [1 2 3]) [1 2 3])) (assert (= (r/cat [4 5 6] []) [4 5 6])) (assert (= (into [] (r/cat [1 2 3] [4 5 6])) [1 2 3 4 5 6])) ;; 引数なしだと、ArrayListが返ってくるみたい (assert (= (r/cat) (java.util.ArrayList.)))
;; drop (assert (= (into [] (r/drop 1 [1 2 3])) [2 3]))
remove
;; remove (assert (= (into [] (r/remove odd? [1 2 3 4 5])) [2 4]))
take
;; take (assert (= (into [] (r/take 3 [1 2 3 4 5 6])) [1 2 3]))
take-while
;; take-while (assert (= (into [] (r/take-while #(<= % 5) [1 2 3 4 5 6 7 8 9 10])) [1 2 3 4 5]))
並列化の話はまったく確認していませんが、とりあえず簡単な使い方はわかりましたということで。