日曜プログラミング

休日趣味でやってるプログラミング関連記事をダラダラと

1.5 の clojure.core.reducers って速くなっただけ?

github の changes.md を最初チラ読みした時は Java7 の fork/join フレームワーク使って
裏で並行処理化するようになって早くなったよ、ってくらいしか目に付かなくて、
元々マルチコアでの並列処理にはそこまで興味なかったんでふーんとしか思ってなかった。

んで、今日作者のブログポスト見てたらそことは別の点で個人的に注目すべき所が
あったような気がしたのでちょっと書いてみる。

まず、changes.md に書かれてるサンプルの最初。

user=> (require '[clojure.core.reducers :as r])
user=> (reduce + (r/filter even? (r/map inc [1 1 1 2])))
; => 6

見た目従来の filter や map と変わらないんじゃと思い従来版を試す。

user=> (reduce + (filter even? (map inc [1 1 1 2])))
; => 6

うん、変わらんね。じゃ次。

;;red is a reducer awaiting a collection
user=> (def red (comp (r/filter even?) (r/map inc)))
user=> (reduce + (red [1 1 1 2]))

同様に元ので試してみる。

user=> (def red (comp (filter even?) (map inc)))
ArityException Wrong number of args (1) passed to: core$filter  clojure.lang.AFn.throwArity (AFn.java:437)

あれ?ああそうか、ここ違う。従来のでやろうとするとこうしないとダメなんだ。

user=> (def red (comp (partial filter even?) (partial map inc)))
user=> (reduce + (red [1 1 1 2]))
; => 6

まあここまでだと(r/filter even?)は新しい方だと関数を返すようになってんだなと
何となく想像がつくものの partial 省略できるようになってるだけじゃと思わなくもない。

面白いのが以下を評価した時。

user=> (r/map inc [1 1 1 2])
; => #<reducers$folder$reify__1122 clojure.core.reducers$folder$reify__1122@21c7ec60>

実際のシーケンス与えても関数を返すんですな。
じゃどうやって結果のシーケンスもらったらいいんだよとなると、ここで最後の 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)

reduce で畳み込んだ結果だけもらっても良し。

ブログを読んでると新しい reducer はシーケンスに対してどう料理するかのレシピを作ってるだけだ
みたいな事を書いてあったがその辺りはまだ良く理解できてないのでまた気が向いた時にでも。

ただ、シーケンスに対して map してーの filter してーの・・・って Clojure の勉強本レベルでも
わりかし良くあるので、comp でシーケンスに対する処理をまとめてつないでってのは
すっきり書けるようになるような気がする。そんで気づいたら並行処理で動作速度も
上がってましたテヘってのは割といいかも。