私はこれを読んでみましたが、それらの価値またはそれらが何を置き換えるかをまだ理解していません。そして、彼らは私のコードをより短く、より理解しやすいものにしますか?
多くの人が回答を投稿しましたが、非常にシンプルなもののためのトランスデューサーの有無の例を見るといいでしょう。私のような馬鹿でも理解できます。もちろん、トランスデューサーに特定の高いレベルの理解が必要な場合を除き、その場合は理解できません:(
トランスデューサーは、基礎となるシーケンスが何であるか(方法)を知らずに、データのシーケンスをどう処理するかを示すレシピです。任意のシーケンス、非同期チャネル、または監視可能です。
それらは構成可能で多形的です。
利点は、新しいデータソースが追加されるたびにすべての標準コンビネータを実装する必要がないことです。何回も何回も。結果として、ユーザーは異なるデータソースでこれらのレシピを再利用できます。
Clojureの以前のバージョン1.7では、データフロークエリを記述する方法が3つありました。
(reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
(def xform
(comp
(partial filter odd?)
(partial map #(+ 2 %))))
(reduce + (xform (range 0 10)))
(defn xform [xs]
(->> xs
(map #(+ 2 %))
(filter odd?)))
(reduce + (xform (range 0 10)))
トランスデューサーを使用すると、次のように記述できます。
(def xform
(comp
(map #(+ 2 %))
(filter odd?)))
(transduce xform + (range 0 10))
すべて同じです。違いは、トランスデューサーを直接呼び出すことはなく、それらを別の関数に渡すことです。トランスデューサーは何をすべきかを知っており、トランスデューサーを取得する機能はその方法を知っています。コンビネータの順序は、スレッドマクロを使用して記述するのと同じです(自然順序)。これで、チャンネルでxform
を再利用できます:
(chan 1 xform)
変換器は効率を改善し、よりモジュール化された方法で効率的なコードを書くことができます。
古いmap
、filter
、reduce
などへの呼び出しを作成する場合と比較して、各ステップの間に中間コレクションを構築し、繰り返し歩く必要がないため、パフォーマンスが向上します。それらのコレクション。
reducers
と比較したり、すべての操作を1つの式に手動で構成したりすると、抽象化、モジュール化、処理機能の再利用が簡単になります。
トランスデューサーは、機能を減らすための組み合わせの手段です。
例:簡約関数は、これまでの結果と入力という2つの引数を取る関数です。 (今のところ)新しい結果を返します。たとえば、+
:2つの引数を使用すると、最初の引数をこれまでの結果、2番目の引数を入力と考えることができます。
トランスデューサーは、+関数を使用して、2回プラス関数にすることができます(追加する前にすべての入力を2倍にします)。これは、そのトランスデューサがどのように見えるかです(最も基本的な用語で):
(defn double
[rfn]
(fn [r i]
(rfn r (* 2 i))))
rfn
を+
に置き換えると、+
が2回プラスに変換される方法を確認できます。
(def twice-plus ;; result of (double +)
(fn [r i]
(+ r (* 2 i))))
(twice-plus 1 2) ;-> 5
(= (twice-plus 1 2) ((double +) 1 2)) ;-> true
そう
(reduce (double +) 0 [1 2 3])
12になります。
トランスデューサーから返される還元関数は、結果を累積する方法とは無関係です。これは、還元関数が知らず知らずに渡されたときに累積するためです。ここでは、+
の代わりにconj
を使用します。 Conj
はコレクションと値を取り、その値が追加された新しいコレクションを返します。
(reduce (double conj) [] [1 2 3])
[2 4 6]を生成します
また、入力がどのようなソースであるかにも依存しません。
複数のトランスデューサーを(チェーン化可能な)レシピとしてチェーン化して、低減機能を変換できます。
更新:公式ページがあるので、読むことを強くお勧めします: http://clojure.org/transducers
一連の関数を使用してデータのストリームを変換するとします。 Unixシェルでは、パイプ演算子を使用してこの種のことを行うことができます。
cat /etc/passwd | tr '[:lower:]' '[:upper:]' | cut -d: -f1| grep R| wc -l
(上記のコマンドは、ユーザー名に大文字のrが含まれるユーザーの数をカウントします)。これは一連のプロセスとして実装され、それぞれが前のプロセスの出力から読み取るため、4つの中間ストリームがあります。 5つのコマンドを単一の集約コマンドに構成する別の実装を想像できます。このコマンドは、入力から読み取り、出力を1回だけ書き込みます。中間ストリームが高価であり、構成が安価であれば、それは良いトレードオフになるかもしれません。
Clojureについても同じことが言えます。変換のパイプラインを表現する方法は複数ありますが、その方法によっては、ある関数から次の関数に渡される中間ストリームになる場合があります。大量のデータがある場合、それらの関数を単一の関数にまとめる方が高速です。変換器を使用すると簡単にできます。 Clojureの以前の革新であるレデューサーは、それを可能にしますが、いくつかの制限があります。トランスデューサーはこれらの制限の一部を取り除きます。
したがって、質問に答えるために、トランスデューサーは必ずしもコードを短くしたり、理解しやすくしたりすることはありませんが、おそらくコードも長くも理解しにくいこともありません。もっと早く。
これ はトランスデューサーのかなり良い概要です。
Rich HickeyがStrange Loop 2014カンファレンスで「トランスデューサー」講演を行いました(45分)。
彼は、トランスデューサーが何であるかを簡単な方法で説明します。実際の例では、空港でバッグを処理します。彼はさまざまな側面を明確に分離し、現在のアプローチと比較しています。最後に、彼は彼らの存在の理論的根拠を与えます。
transducers-js の例を読むと、日常のコードでどのように使用できるかを具体的に理解するのに役立ちます。
たとえば、次の例を考えてみてください(README上記のリンクから取得):
var t = require("transducers-js");
var map = t.map,
filter = t.filter,
comp = t.comp,
into = t.into;
var inc = function(n) { return n + 1; };
var isEven = function(n) { return n % 2 == 0; };
var xf = comp(map(inc), filter(isEven));
console.log(into([], xf, [0,1,2,3,4])); // [2,4]
たとえば、xf
を使用すると、Underscoreを使用した通常の代替手段よりもずっときれいに見えます。
_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);
変換器は(私の理解では!)1つのreducing関数を取り、別の関数を返す関数です。還元機能は
例えば:
user> (def my-transducer (comp count filter))
#'user/my-transducer
user> (my-transducer even? [0 1 2 3 4 5 6])
4
user> (my-transducer #(< 3 %) [0 1 2 3 4 5 6])
3
この場合、my-transducerは値が偶数の場合、0に適用される入力フィルタリング関数を取りますか?最初の場合、フィルターはその値をカウンターに渡し、次の値をフィルターします。最初にフィルタリングしてから、それらすべての値をcountに渡す代わりに。
2番目の例でも同じです。一度に1つの値をチェックし、その値が3未満の場合、countに1を加算します。
トランスデューサーの明確な定義は次のとおりです。
Transducers are a powerful and composable way to build algorithmic transformations that you can reuse in many contexts, and they’re coming to Clojure core and core.async.
それを理解するために、次の簡単な例を考えてみましょう。
;; The Families in the Village
(def village
[{:home :north :family "smith" :name "sue" :age 37 :sex :f :role :parent}
{:home :north :family "smith" :name "stan" :age 35 :sex :m :role :parent}
{:home :north :family "smith" :name "simon" :age 7 :sex :m :role :child}
{:home :north :family "smith" :name "sadie" :age 5 :sex :f :role :child}
{:home :south :family "jones" :name "jill" :age 45 :sex :f :role :parent}
{:home :south :family "jones" :name "jeff" :age 45 :sex :m :role :parent}
{:home :south :family "jones" :name "jackie" :age 19 :sex :f :role :child}
{:home :south :family "jones" :name "jason" :age 16 :sex :f :role :child}
{:home :south :family "jones" :name "june" :age 14 :sex :f :role :child}
{:home :west :family "brown" :name "billie" :age 55 :sex :f :role :parent}
{:home :west :family "brown" :name "brian" :age 23 :sex :m :role :child}
{:home :west :family "brown" :name "bettie" :age 29 :sex :f :role :child}
{:home :east :family "williams" :name "walter" :age 23 :sex :m :role :parent}
{:home :east :family "williams" :name "wanda" :age 3 :sex :f :role :child}])
村に何人の子供がいるのか知りたいですか?次のレデューサーで簡単に見つけることができます。
;; Example 1a - using a reducer to add up all the mapped values
(def ex1a-map-children-to-value-1 (r/map #(if (= :child (:role %)) 1 0)))
(r/reduce + 0 (ex1a-map-children-to-value-1 village))
;;=>
8
これを行う別の方法を次に示します。
;; Example 1b - using a transducer to add up all the mapped values
;; create the transducers using the new arity for map that
;; takes just the function, no collection
(def ex1b-map-children-to-value-1 (map #(if (= :child (:role %)) 1 0)))
;; now use transduce (c.f r/reduce) with the transducer to get the answer
(transduce ex1b-map-children-to-value-1 + 0 village)
;;=>
8
また、サブグループを考慮する場合にも非常に強力です。たとえば、ブラウンファミリーにいる子供の数を知りたい場合は、次を実行できます。
;; Example 2a - using a reducer to count the children in the Brown family
;; create the reducer to select members of the Brown family
(def ex2a-select-brown-family (r/filter #(= "brown" (string/lower-case (:family %)))))
;; compose a composite function to select the Brown family and map children to 1
(def ex2a-count-brown-family-children (comp ex1a-map-children-to-value-1 ex2a-select-brown-family))
;; reduce to add up all the Brown children
(r/reduce + 0 (ex2a-count-brown-family-children village))
;;=>
2
これらの例を参考にしてください。もっと見つけることができます こちら
それが役に立てば幸い。
クレメンシオ・モラレス・ルーカス。
これについては、clojurescript example でブログに書きました。これは、還元機能を置き換えることができるようになったことで、シーケンス機能がどのように拡張できるかを説明しています。
これがトランスデューサのポイントです。 cons
、conj
などの操作でハードコーディングされたmap
またはfilter
操作について考えると、縮小関数は到達不能でした。
トランスデューサーでは、還元機能が分離されており、トランスデューサーのおかげでネイティブのJavaScript配列Push
で行ったように置き換えることができます。
(transduce (filter #(not (.hasOwnProperty prevChildMapping %))) (.-Push #js[]) #js [] nextKeys)
filter
とその友人には、独自の還元関数を提供するために使用できる変換関数を返す新しい1アリティ演算があります。
これが私の(大部分の)専門用語とコードフリーの回答です。
データは、ストリーム(イベントなどの経時的に発生する値)または構造(リスト、ベクトル、配列などの特定の時点で存在するデータ)の2つの方法で考えてください。
ストリームまたは構造に対して実行したい特定の操作があります。そのような操作の1つがマッピングです。マッピング関数は、各データ項目を1ずつ増加させることがあり(数値であると仮定)、これがストリームまたは構造のいずれに適用されるか想像できます。
マッピング関数は、「リデュース関数」と呼ばれることもある関数のクラスの1つにすぎません。別の一般的な削減関数は、述語に一致する値を削除するフィルターです(たとえば、偶数の値をすべて削除します)。
トランスデューサーを使用すると、1つ以上の還元関数のシーケンスを「ラップ」し、ストリームまたは構造の両方で機能する「パッケージ」(それ自体が関数)を生成できます。たとえば、一連の還元関数を「パッケージ化」して(例えば、偶数のフィルター処理を行い、結果の数値をマップして1ずつインクリメントする)、そのトランスデューサーをストリームまたは値の構造(またはその両方)で使用します。 。
それで、これについて特別なことは何ですか?通常、ストリームと構造の両方で機能するように効率的に構成できない関数を削減します。
したがって、これらの機能に関する知識を活用して、より多くのユースケースに適用できるという利点があります。コストは、この追加のパワーを提供するために、いくつかの追加の機械(つまり、トランスデューサー)を学習する必要があることです。
私の知る限り、それらはビルディングブロックのようなもので、入力および出力の実装から切り離されています。操作を定義するだけです。
操作の実装は入力のコードに含まれておらず、出力では何も行われないため、トランスデューサは非常に再利用可能です。彼らはAkka StreamsのFlowsを思い出させます。
私もトランスデューサーは初めてですが、不明瞭な答えをすみません。
この投稿は、トランスデューサの鳥瞰図を提供するものです。
https://medium.com/@roman01la/understanding-transducers-in-javascript-3500d3bd9624