Clojure 1.7から導入されているTransducerの初期処理に関するメモ。
公式ドキュメント Creating Transducers ではTransducerとは以下のようなアリティ0〜2でオーバーロードされた関数を返す高階関数として説明されている。
引数 xf
にはReducing step functionもしくは関数合成時にラップする他のTransducerが渡される。
アリティ0には初期処理、アリティ1には終了処理、そしてアリティ2には変換処理を記述する。
(fn [xf]
(fn ([] ...)
([result] ...)
([result input] ...)))
初期処理とは?
今回気になって調べたのがアリティ0の初期処理について。
公式ドキュメントには以下にように説明されている。
Init (arity 0) - should call the init arity on the nested transform xf, which will eventually call out to the transducing process.
今までは漠然とTransducerが状態を持つことも出来る様に用意された初期処理だと思っていたのだが、改めて考えるとこの関数が存在する理由が気になってきた。
例えば状態を持つTransducerを書こうとすると以下のようにTransducer生成時のレキシカルスコープ上で状態を束縛する感じになると思うのだが、
(defn stateful-xform []
(let [state ...]
(fn [xf]
...
)))
この状態の初期処理をあえてこの関数に切り出す事にどういう意味があるのだろうか?
何のために?
この切り出された初期化関数に意味があるとすれば初期化を任意のタイミングで行うことが出来る点だ。
そしてそれは繰り返し行うことも出来る。
もしやTransducerインスタンスの再利用を許容するための仕組みなのだろうか?
いずれにしてもこの関数は外部から操作することはできないので transduce や sequence 等の実際にTransducerを利用する関数内から呼び出され、処理を行う前に必ずTransducerインスタンスが初期化されるという塩梅になっているはず…
と思いきや実際にソースを確認してみてもそれらしき箇所が見当たらない。
clojure.coreの状態を持つTransducerはどうしている?
clojure.coreの状態を持つTransducerはどうなっているのだろうか?
dedupe や partition-all 等のソースを確認してみるがいずれも
(fn [xf]
(fn ([] (xf))
...)
となっており状態の初期化は無し、ネストしたTransducerの初期処理を呼び出しているのみだった。
またclojure.coreの状態を持たないTransducerのアリティ0の関数を確認してみたがいずれも同様でだった。
状況をまとめる
- Transducerのアリティ0の関数は公式ドキュメントで初期処理用関数として定義されている
- clojure.coreのTransducerは初期処理用関数を必ず実装されているが内容は一律ネストしたTransducerの呼び出しのみ
- 状態を持つTransducerの再利用は考慮されていない
- そもそも初期処理用関数は定義しても実際に呼びだされない。呼び出しを行う側のコードも見つからない
結論
よくわからない。
- Understanding init (the zero arity function) for transducers.
- [CLJ-1569] transduce does not respect the init arity of transducers
ここら辺を読んでもやっぱりわからない。
一旦自分としては、
Transducerには形式上アリティ0の初期処理関数を用意するが、実際には呼ばれないので初期処理はそこに書かない
という謎の結論でこの件は終わらせようと思う。