トランスデューサーを使用した効率的なデータ変換

画像



大量のデータの変換は、特にマップやフィルターなどの高次関数を使用する場合、非常にリソースを消費します。







この記事では、一時的な配列を作成しない効果的なデータ変換関数を作成するコンバーターの機能を示します。 一時的なコレクションは、互いに接続されたマップおよびフィルター機能用に作成されます。 これは、これらの関数が新しいコレクションに戻り、次の関数の結果を返すためです。







人口が1,000,000人で、「オランダに住んでいる18歳以上の女性の名前」のサブセットを作成する必要があるとします。 この問題を解決するにはさまざまな方法がありますが、まずはチェーンから始めます。







const ageAbove18 = (person) => person.age > 18; const isFemale = (person) => person.gender === 'female'; const livesInTheNetherlands = (person) => person.country === 'NL'; const pickFullName = (person) => person.fullName; const output = bigCollectionOfData .filter(livesInTheNetherlands) .filter(isFemale) .filter(ageAbove18) .map(pickFullName);
      
      





以下は、一時的な配列を作成するチェーンアプローチを使用して問題を解決する例です。 1,000,000を超えるエントリでのトリプルループのコストを想像してください。







画像

もちろん、フィルター処理されたコレクションはわずかに削減されますが、これは依然として非常に高価です。







ただし、主なポイントは、reduceを使用してマップとフィルターを定義できることです。 上記のコードを略語形式で実装してみましょう。







 const mapReducer = (mapper) => (result, input) => { return result.concat(mapper(input)); }; const filterReducer = (predicate) => (result, input) => { return predicate(input) ? result.concat(input) : result; }; const personRequirements = (person) => ageAbove18(person) && isFemale(person) && livesInTheNetherlands(person); const output = bigCollectionOfData .reduce(filterReducer(personRequirements), []) .reduce(mapReducer(pickFullName), []);
      
      





さらに、関数合成を使用することで、リダクション(filterReducer)をさらに簡素化できます。







 filterReducer(compose(ageAbove18, isFemale, livesInTheNetherlands));
      
      





このアプローチを使用して、一時配列の数を減らします(笑)。 以下は、短縮アプローチを使用した変換の例です。







画像

きれいですね。 しかし、トランスデューサーについて話しました。 トランスデューサはどこにありますか?

作成したfilterReducerとmapReducerが関数を短縮することがわかりました。 これは次のように表現できます。







 reducing-function :: result, input -> result
      
      





変換器は、略語を受け入れて略語を返す関数です。 これは次のように表現できます。







 transducer :: (result, input -> result) -> (result, input -> result)
      
      





最も興味深いのは、トランスデューサーのタイプシグネチャがほぼ対称であることです。 短縮関数を1つ受け取り、別の短縮関数を返します。

したがって、関数の構成を使用して、任意の数の減速機を構成できます。







独自のトランスデューサーを構築します。



これがすべて明確になったことを願っています。 次に、マップとフィルター用に独自のトランスデューサー関数を取得してみましょう。







 const mapTransducer = (mapper) => (reducingFunction) => { return (result, input) => reducingFunction(result, mapper(input)); } const filterTransducer = (predicate) => (reducingFunction) => { return (result, input) => predicate(input) ? reducingFunction(result, input) : result; }
      
      





上記で作成したレデューサーを使用して、いくつかの数値を変換しましょう。 RamdaJSライブラリの構成機能を使用します。 RamdaJSは実用的で機能的なメソッドを含むライブラリであり、特に機能的なプログラミングスタイル用に設計されています。







 const concatReducer = (result, input) => result.concat(input); const lowerThan6 = filterTransducer((value) => value < 6); const double = mapTransducer((value) => value * 2); const numbers = [1, 2, 3]; // Using Ramda's compose here const xform = R.compose(double, lowerThan6); const output = numbers.reduce(xform(concatReducer), []); // [2, 4]
      
      





concatReducer 関数は、 反復関数と呼ばれます 。 各反復で呼び出され、トランスデューサーの出力関数を変換します。







この例では、結果を単純に連結します。 各トランスデューサーはリダクション関数のみを受け入れるため、value.concatは使用できません。







複数のコンバーターを1つの関数に構成する場合、ほとんどの場合、xformトランスデューサーと呼ばれます。 したがって、どこかで見ると、その意味がわかります。







いくつかのコンバーターのコンパイル。



前の例では関数の通常の構成を使用しましたが、この評価の順序はどうなっているのか疑問に思われるかもしれません。 関数の構成は右から左に適用されますが、変換は実行時に左から右に評価されるため、左から右に読む人にとってはより直感的です。







これがなぜそうなのかを理解するには、少し反省する必要があります:ダブルトランスデューサーがリダクション関数を返し、lowerThan6もリダクション関数を返すことを考えると、それらが接続されると、ダブル値がlowerThan6に転送され、その結果lowerThan6関数が取得されます。 したがって、doubleは合成の結果であり、評価順序は左から右に適用されます。







あなたが自分でそれを見ることができるように、私は例を作成しまし







RamdaJSを使用して読みやすさを最適化します。



トランスデューサーは関数型プログラミングスタイルの好例なので、Ramdaの一連のメソッドがどのように役立つかを見てみましょう。







 const lowerThan6 = R.filter((value) => value < 6); const double = R.map((value) => value * 2); const numbers = [1, 2, 3]; const xform = R.compose(double, lowerThan6); const output = R.into([], xform, numbers); // [2,4]
      
      





Ramdaでは、マップとフィルターを使用できます。 これは、Ramdaの内部減速方式が組み込みのTransducer Protocolを使用しているためです。







「このプロトコルの目的は、トランスデューサのすべてのJavaScript実装で、APIの表面レベルに関係なく相互作用が発生することです。 入力および出力のソースのコンテキストに関係なくトランスフォーマーを呼び出し、個々の要素の観点からトランスフォーメーションの本質のみを示します。

トランスデューサーは入力および出力ソースから分離されているため、コレクション、ストリーム、チャンネル、ブラウザーなど、さまざまなプロセスで使用できます。コンバーターは、入力または中間ユニットの作成を参照せずに直接組み立てられます。


おわりに



変換器は、多くのコンテキストで使用できる高品質の変換を構築するための強力で可変的な方法です。 変換器はあなたの手の中にあります-変​​換の機会はたくさんあります。







また、大量のデータを変換する場合は特に生産的ですが、同じコンバーターを使用して単一のレコードを処理することもできます。







このトピックの詳細については、次の記事を参照してください。

https://clojure.org/reference/transducers

http://blog.cognitect.com/blog/2014/8/6/transducers-are-coming

https://github.com/cognitect-labs/transducers-js#the-transducer-protocol








All Articles