JavaScriptのトランスデューサー。 パート1

Clojureの著者であるRich Hickeyが最近、新しい概念Transducersを思いつきました 。 それらはすぐにClojureに追加されましたが、アイデア自体は普遍的であり、他の言語で再現できます。



すぐに、なぜこれが必要なのですか:







トランスデューサーは、map()、filter()などのコレクションに対する操作を再考し、それらの中で共通のアイデアを見つけ、さらに再利用するためにいくつかの操作を組み合わせることを学習しようとする試みです。







いくつかの操作を組み合わせる方法はすでに知っています。



function mapFilterTake(coll) { return _.take(_.filter(_.map(coll, mapFn), filterFn), 5); } // (       underscore.js)
      
      







しかし、いくつかの問題があります。







トランスデューサーの概念を説明するには、リデュース操作から始める必要があります。 考えてみると、コレクションに対する操作はreduceで表現できます。 地図操作から始めましょう。



 function append(coll, item) { return coll.concat([item]); } var newColl = _.reduce(coll, function(result, item) { return append(result, mapFn(item)); }, []); //    map var newColl = _.map(coll, mapFn);
      
      







空の配列から始めて、結果を追加します。元の配列の各要素をmapFn関数に渡し、結果を結果の配列に追加します。



また、ユーティリティ関数append()を追加しました。これは、単に.concat()をラップするだけで、これがなぜ必要なのかが明らかになります。



次に、reduceを介してフィルターを表現します。



 var newColl = _.reduce(coll, function(result, item) { if (filterFn(item)) { return append(result, item); } else { return result; } }, []); //    filter var newColl = _.filter(coll, filterFn);
      
      







ここでもすべてが明確であることを願っています。



次に、.take()について説明する必要がありますが、これではすべてが少し複雑になります。これについては、記事の第2部で説明し、フィルターとマップについて説明します。



それでは、マップとフィルターをシミュレートするためにreduceに渡す関数を詳しく見てみましょう。



 function(result, item) { return append(result, mapFn(item)); } function(result, item) { if (filterFn(item)) { return append(result, item); } else { return result; } }
      
      







それらは同じタイプの受け入れ値と戻り値を持っています。つまり、マップとフィルターに共通するものをすでに見つけており、正しい方向に進んでいます。 しかし、1つの問題があります。内部でappend()関数を使用します。この関数は配列でのみ機能し、その結果、これらの関数自体も配列でのみ機能します。 append()を引き出しましょう。



 function(step) { return function(result, item) { return step(result, mapFn(item)); } } function(step) { return function(result, item) { if (filterFn(item)) { return step(result, item); } else { return result; } } }
      
      







これらの各関数は、特定のstep()関数を取り、既製のreduceハンドラーを返す追加の関数でラップしました。 今後は、これがトランスデューサー、つまり ステップを受け入れてハンドラーを返す関数はトランスデューサーです。



すべてが機能している間、それを確認しましょう。



 var mapT = function(step) { return function(result, item) { return step(result, mapFn(item)); } } var filterT = function(step) { return function(result, item) { if (filterFn(item)) { return step(result, item); } else { return result; } } } var newColl = _.reduce(coll, mapT(append), []); var newColl = _.reduce(coll, filterT(append), []);
      
      







うまくいくようです:-)

ここで、mapTとfilterTは「マップトランスデューサー」と「フィルタートランスデューサー」を意味します。



先に進む前に、さまざまなタイプのトランスデューサーを生成する関数を作成しましょう(これまではマップとフィルターのみ)。



 function map(fn) { return function(step) { return function(result, item) { return step(result, fn(item)); } } } function filter(predicate) { return function(step) { return function(result, item) { if (predicate(item)) { return step(result, item); } else { return result; } } } } //     var addOneT = map(function(x) {return x + 1}); var lessTnan4T = filter(function(x) {return x < 4}); _.reduce([1, 2, 3, 4], addOneT(append), []); // => [2, 3, 4, 5] _.reduce([2, 3, 4, 5], lessTnan4T(append), []); // => [2, 3]
      
      







step()関数のパラメーターを見ると、トランスデューサーが返す関数(reduceに渡すもの)とまったく同じタイプのパラメーターと戻り値を持っていることがわかります。 これは、複数のトランスデューサーを1つに結合できるため、非常に重要です!



 var addOne_lessTnan4 = function(step) { return addOneT(lessTnan4T(step)); } // ,   ,    _.compose var addOne_lessTnan4 = _.compose(addOneT, lessTnan4T); // , ,      _.reduce([1, 2, 3, 4], addOne_lessTnan4(append), []); // => [2, 3]
      
      







そのため、コレクションを操作するための関数を新しい方法で組み合わせる方法を学び、トランスデューサーを組み合わせた結果として取得するオブジェクトに名前を付けました。 しかし、記事の冒頭で発表された問題を解決することはできましたか?



1)mapFilterTake()は特定のタイプのコレクションでのみ機能します



addUne_lessTnan4トランスデューサーは、強制的に処理するコレクションのタイプについて何も知りません。

別のデータ型を使用できます。 たとえば、配列ではなくオブジェクトを取得するには、

append関数と初期値[]を置き換えるだけです。



 _.reduce([1, 2, 3, 4], addOne_lessTnan4(function(result, item) { result[item] = true; return result; }), {}); // => {2: true, 3: true}
      
      







入力データのタイプを変更するには、_。reduce()の代わりに、このタイプを反復処理できる別の関数を使用する必要があります。 これも難しいことではありません。



2)mapFilterTake()は遅延スタイルでは使用できません



トランスデューサーを使用してコレクションを処理する場合、一時的なコレクションは作成されず、各要素は最初から最後まで完全に処理されるため、まだ必要のない要素は処理されない場合があります。 つまり _.reduce()に似たメソッドを作成できます。このメソッドはすぐに結果を返しませんが、.getNext()を呼び出して次の処理済み要素を取得できます。 または、何らかの方法で怠inessを整理することもできます。



3)mapFilterTake()は、大規模なコレクションではゆっくり動作します



明らかに、ここではすべてがトランスデューサーによって把握されています。



4)FRP / CSPライブラリでmapFilterTake()を使用できません



トランスデューサーは、処理中のコレクションのタイプに関連付けられておらず、中間結果を作成しないため、イベントフローや動作/プロパティなどのコレクションでも使用できます。 FRPに似たCSPアプローチでも使用できます。 そして、潜在的にそれはまだ新しいもので使用することが可能になるでしょう。



第2部では、take、takeWhile、およびその他のトランスデューサーの作成方法と、JavaScriptコミュニティでこれをどうするかを説明します。



JavaScriptのトランスデューサー。 パート2



関連リンク:



blog.cognitect.com/blog/2014/8/6/transducers-are-coming-最初の言及(私が間違っていなければ)

phuu.net/2014/08/31/csp-and-transducers.html-JavaScriptの CSPとトランスデューサーについて

jlong​​ster.com/Transducers.js--A-JavaScript-Library-for-Transformation-of-Data-JavaScriptトランスデューサーについてもう一度、CSPについて少し

www.youtube.com/watch?v=6mTbuzafcII-リッチヒッキーがトランスデューサーについて詳しく語る



All Articles