(2019/06/11 追記: 全体修正しました)
こんにちは。
本回答は「Transducers という概念がどのようなものか」という質問に対するものとします。
まず簡単に説明すると、質問内参照の記事が言っている Transducers の概念とは、
「reduce が使う関数 (acc, a) => acc
を composable にする」
というものです。
上から2番目のリンクの記事のコードが最も理解しやすいと思うので、書かれているコードを順番に一つずつ追いかけてみると理解につながると思います。
以下、Transducders の概念の解説です。
関数型言語における中心的な概念を表した一つに、map/reduce というものがあるのは質問者さんも知っていると思います。
この map というのは射影変換、特定のデータ構造 (例えば集合、Array) に対して、要素を変換する関数を Array から Array への変換関数に格上げするものです。既に使っている Array.map はまさに配列の要素に対して一括変換するものですね。
そして reduce というのは、Array などのデータ構造を「処理」する関数によって「集計」「押し潰し」を行う関数と言えます。
Transducers はこの reduce に着目した概念です。
まずは前提として、reduce に渡す関数 (acc, a) => acc
に reducer
という別名を付けておきます。つまり、Array.reduce は reducer を使ってデータ構造を集計する関数、という意味になります。
ここで、任意の reducer を渡すとそれを用いて新たな reducer を生成する関数 reducer => reducer
を想定します。これに transducer
という名前を付けます。
このとき、compose
の型は、
compose : transducer => transducer => transducer
compose : (reducer => reducer) => (reducer => reducer) => (reducer => reducer)
となります。
複数の transducer を合成して一つの transducer にする関数であることを意味します。
となると、Transducers における map
や filter
が返すべきものは transducer
、つまり reducer => reducer
である必要があります。
map : (a => b) => transducer
map : (a => b) => reducer => reducer
map : (a => b) => ((acc, b) => acc) => ((acc, a) => acc)
filter : (a => Bool) => transducer
filter : (a => Bool) => reducer => reducer
filter : (a => Bool) => ((acc, a) => acc) => ((acc, a) => acc)
型として表すとこうなります。
これらの transducer を compose で合成し、出来上がった transducer に最終結果を得るための reducer : (acc, a) => acc
を渡すことで (acc, a) => acc
が得られます。これを reduce で利用するのが Transducers の仕組みです。
あなたのコードでは、
filter : (a => Bool) => Array => Array
map : (a => b) => Array => Array
transducer : Array => Array
となっており、これでは根本的に別物となっています。
型シグネチャとして見ると、標準の射影関数と同等となっていることが分かると思います。つまり、Array に対してそれぞれが射影を行っているため、実行される度に全要素が処理された中間データが生成されています。
Transducers の理解 3. の内容自体は正しいですが、コードでは全くそうなっていないのです。
質問のコードを展開すると、動いているのはただの arr.map(add1).filter(even)
となっています。
transducer を用いて生成される reducer は、ただ1回の reduce を実行する際に、1要素毎に合成した各関数を実行する (質問に引用している画像の通りです) からこそ、中間データを生成しないのです。
まとめると、
- Transducers は、reduce に渡す関数
(acc, a) => acc
を合成可能にする概念
- map や filter、その他 Transducers の関数群は、それぞれ任意の関数や値を引数に取って transducer を返す関数である
- 合成した transducer に任意の reducer を渡すことで reducer が得られ、それを
arr.reduce()
で使用することで動作する
以上の説明の元で、質問にある定義について注釈を入れると、
- compose()して返却された関数構成群がtransducerである。
compose に「渡すもの」こそが transducer で、だからこそ合成されたものも transducer なのです。compose はその名の通り「合成」だけを意味しており、transducer を生成するような意味はありません。
- transducerを実行するには、❶transduce()に渡す以外にも、❷transducer()に配列を渡す事でも実行できる。
transducer は配列を取る関数ではないです。reducer を受け取って reducer を返す関数なので、transducer に (acc, a) => acc を渡して、返ってきた (acc, a) => acc を Array.reduce に渡すことで初めて実行されます。
transduce は、ソースと transducer と reducer と initValue をまとめて取るだけの単なる糖衣関数です。
- ❷の内部処理において、中間値が生成されるような処理ではなく、下画像のような効率的な処理がなされる。
これは正しいです。transducer は全ての処理が合成された1つの reducer を生成するため、reduce 処理の実行時に各要素毎に処理され、中間データは生成されません。