CLOVER🍀

That was when it all began.

Clojure Reducers

かなり久々のClojureネタです。

以前から、試してみたいと思いつつ放っておいた、ClojureのReducersについて。

http://clojure.org/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

;; 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]))

並列化の話はまったく確認していませんが、とりあえず簡単な使い方はわかりましたということで。