clojure1.7のtransducersの中身を見てみる
続き。
もう少しtransducersの中身を見ていきます。
まず、前に取り上げたmapですが、実際のコードはこうなってます。
(transducerの部分のみ抜粋)
([f]
(fn [f1]
(fn
([] (f1))
([result] (f1 result))
([result input]
(f1 result (f input)))
([result input & inputs]
(f1 result (apply f input inputs))))))
mapは、関数を返す関数を返す関数です。(mapにかぎらず、transducerを返す関数はもちろんそうです)
fはmapの第一引数、mapping functionです。で、f1がtransducerの入力となるreducing functionです。transducerはreducing functionを入力に受けて、別のreducing functionを返す関数なので、f1はこの入力にあたります。
一番大事っぽい部分は2引数を受け取る
([result input]
(f1 result (f input)))
の部分ですね。元のreducing functionf1にresultと、inputをmapping functionfで変換した値を渡しています。
これがmapping transducerです。
ではもう一つ、dropを見てみます。
dropは従来は
(drop 1 [1 2 3])
; => (2 3)
第一引数の数だけ第二引数のシーケンスの先頭から取り除いたシーケンスを返す関数なのですが、第二引数が省略されるとtransducerを返します。dropping transducerと呼ぶのでしょうか。
(def d (drop 1))
(sequence d [1 2 3])
; => (2 3)
コードはこんな感じ。
([n]
(fn [f1]
(let [na (atom n)]
(fn
([] (f1))
([result] (f1 result))
([result input]
(let [n @na]
(swap! na dec)
(if (pos? n)
result
(f1 result input))))))))
これはちょっとおもしろいですね。
dropの第一引数をatomでnaにとっておいて、dropping transducerによって作られるreducing functionは呼ばれる度にnaを-1していきます。naが正数の間はreducing functionf1を適用せずにresultをそのまま返すことで、dropの動きを実現しています。
drop以外にもtake-nthとか、長さを変える系のtransducerはこういう仕組みになってます。
transducerを受け取ってtransduceする側の関数も紹介しようかと思ったけどここで力尽きた