私はこれを読んでみましたが、それでもそれらの価値やそれらが何を置き換えるのかわかりません。そして、それらは私のコードをより短く、より理解しやすいものにしますか?
更新
多くの人が回答を投稿しましたが、トランスデューサーを使用した場合と使用しない場合の例を非常にシンプルなものにして、それを私のようなばかでさえ理解できるとよいでしょう。もちろんトランスデューサーがある程度の高度な理解を必要としない限り、その場合私はそれらを決して理解しません:(
私はこれを読んでみましたが、それでもそれらの価値やそれらが何を置き換えるのかわかりません。そして、それらは私のコードをより短く、より理解しやすいものにしますか?
多くの人が回答を投稿しましたが、トランスデューサーを使用した場合と使用しない場合の例を非常にシンプルなものにして、それを私のようなばかでさえ理解できるとよいでしょう。もちろんトランスデューサーがある程度の高度な理解を必要としない限り、その場合私はそれらを決して理解しません:(
回答:
トランスデューサーは、基礎となるシーケンスが何か(それを行う方法)を知らなくても、データシーケンスをどう処理するかを示すレシピです。これは、任意のシーケンス、非同期チャネル、またはおそらく監視可能です。
それらは合成可能で多形です。
利点は、新しいデータソースが追加されるたびにすべての標準コンビネータを実装する必要がないことです。何回も何回も。結果として得られる効果として、ユーザーはこれらのレシピをさまざまなデータソースで再利用できます。
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
たり、手動ですべての操作を単一の式に構成したりする場合と比べて、抽象化が使いやすくなり、モジュール性が向上し、処理関数を再利用できるようになります。
map
/ reduce
それらのすべてがイテレータチェーンを構築するための中間コレクションを使用します。ここのどこが間違っていますか?
map
とfilter
、中間コレクションをClojure して作成します。
トランスデューサーは、機能を減らすための組み合わせの手段です。
例:縮約関数は、2つの引数を取る関数です。これまでの結果と入力です。彼らは(今のところ)新しい結果を返します。例+
:2つの引数を使用すると、最初の引数をこれまでの結果と見なし、2番目の引数を入力と見なすことができます。
トランスデューサーは+関数を取り、2回プラスの関数にすることができます(追加する前にすべての入力を2倍にします)。これは、そのトランスデューサーがどのように見えるかです(最も基本的な用語で):
(defn double
[rfn]
(fn [r i]
(rfn r (* 2 i))))
どのように2倍以上に変換されるかを確認するためにrfn
withで置き換え+
ます+
。
(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
double
とはtransduce
?
一連の関数を使用してデータのストリームを変換したいとします。Unixシェルでは、パイプ演算子を使用してこのようなことを行うことができます。
cat /etc/passwd | tr '[:lower:]' '[:upper:]' | cut -d: -f1| grep R| wc -l
(上記のコマンドは、ユーザー名に大文字または小文字のいずれかで文字rが付いているユーザーの数をカウントします)。これは一連のプロセスとして実装され、それぞれが前のプロセスの出力から読み取るため、4つの中間ストリームがあります。5つのコマンドを1つの集約コマンドに構成して、入力から読み取り、出力を1回だけ書き込む別の実装を想像できます。中間ストリームが高価であり、構成が安価である場合、それは良いトレードオフになるかもしれません。
同じようなことがClojureにも当てはまります。変換のパイプラインを表現する方法は複数ありますが、その方法によっては、ある関数から次の関数に中間ストリームが渡される場合があります。大量のデータがある場合は、これらの関数を単一の関数に構成する方が高速です。トランスデューサーはそれを行うことを簡単にします。初期のClojureイノベーションであるレデューサーでは、それも可能ですが、いくつかの制限があります。トランスデューサーはそれらの制限のいくつかを取り除きます。
したがって、あなたの質問に答えるために、トランスデューサーは必ずしもコードを短くしたり理解しやすくしたりしませんが、コードもおそらく長くなったり、理解しにくくなったりしません。多くのデータを処理している場合、トランスデューサーはコードを作成できますもっと早く。
これはトランスデューサーのかなり良い概要です。
pmap
はありますが、十分な注目を集めていないようです。map
シーケンスを介して高価な関数をpingする場合、操作を並列化するのは「p」を追加するのと同じくらい簡単です。コードの他の部分を変更する必要はありません。現在利用できます。アルファ版ではなく、ベータ版ではありません。(関数が中間シーケンスを作成する場合、トランスデューサーはより高速になると思います。)
リッチヒッキーは、ストレンジループ2014カンファレンス(45分)で「トランスデューサー」の講演を行いました。
彼はトランスデューサーが何であるかを簡単な方法で説明し、実際の例-空港でのバッグの処理。彼は異なる側面を明確に分離し、それらを現在のアプローチと対比しています。最後に向かって、彼は彼らの存在の論理的根拠を与えます。
私はトランスデューサー-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]
1つは、xf
Underscore を使用する通常の方法よりも使用方法がはるかにきれいに見えることです。
_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);
t.into([], t.comp(t.map(inc), t.filter(isEven)), [0,1,2,3,4])
トランスデューサーは(私の理解では)、ある還元関数を取り、別の還元関数を返す関数です。還元機能とは
例えば:
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に適用される入力フィルタリング関数を使用します。その値が偶数の場合、最初のケースでは、フィルターはその値をカウンターに渡し、次に次の値をフィルターします。最初にフィルタリングしてから、それらの値をすべてカウントに渡す代わりに。
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の例を使ってブログに書いたこれについてを、還元関数を置き換えることができるようになり、シーケンス関数がどのように拡張できるようになるかを説明しています。
これが私が読んだトランスデューサーのポイントです。cons
またはのconj
ような操作にハードコーディングされている操作について考えるとmap
、filter
など、軽減機能が到達不能でした。
トランスデューサーを使用すると、還元機能が分離され、push
トランスデューサーのおかげでネイティブのJavaScript配列と同じようにそれを置き換えることができます。
(transduce (filter #(not (.hasOwnProperty prevChildMapping %))) (.-push #js[]) #js [] nextKeys)
filter
友人たちは、独自の還元関数を提供するために使用できる変換関数を返す新しい1アリティ演算を持っています。
ここに私の(ほとんど)専門用語とコードフリーの答えがあります。
データは、ストリーム(イベントなどの時間の経過とともに発生する値)または構造(リスト、ベクトル、配列など、ある時点で存在するデータ)の2つの方法で考えます。
ストリームまたは構造のいずれかに対して実行する必要がある特定の操作があります。そのような操作の1つがマッピングです。マッピング関数は、各データ項目(数値であると想定)を1ずつ増やす可能性があり、これがストリームまたは構造体にどのように適用されるか想像できます。
マッピング関数は、「還元関数」と呼ばれることもある関数のクラスの1つにすぎません。もう1つの一般的な削減関数は、述語に一致する値を削除するフィルターです(偶数のすべての値を削除するなど)。
トランスデューサーを使用すると、1つ以上のレデューシング関数のシーケンスを「ラップ」して、両方のストリームまたは構造で機能する「パッケージ」(それ自体が関数)を生成できます。たとえば、一連のリダクション関数を「パッケージ化」し(偶数をフィルタリングし、結果の数値をマップして1ずつ増やす)、ストリームまたは値の構造(またはその両方)でそのトランスデューサー「パッケージ」を使用できます。 。
これは何が特別なのでしょうか?通常、削減関数は、ストリームと構造の両方で機能するように効率的に構成することができません。
したがって、これらの機能に関する知識を活用して、より多くのユースケースに適用できるというメリットがあります。あなたにとってのコストは、あなたにこの追加の力を与えるためにいくつかの追加の機械(すなわちトランスデューサー)を学ばなければならないということです。
この投稿では、トランスデューサーの鳥瞰図を紹介しています。
https://medium.com/@roman01la/understanding-transducers-in-javascript-3500d3bd9624