私はこれについて読んでみましたが、それらの価値やそれらが何を置き換えるかをまだ理解していません. そして、それらは私のコードをより短く、より理解しやすくしますか、それとも何ですか?
アップデート
多くの人が回答を投稿しましたが、私のような馬鹿でも理解できる非常に単純なものについて、トランスデューサを使用した場合と使用しない場合の例を見るといいでしょう。もちろん、トランスデューサーが特定の高レベルの理解を必要とする場合を除いて、その場合、私はそれらを理解することは決してありません:(
私はこれについて読んでみましたが、それらの価値やそれらが何を置き換えるかをまだ理解していません. そして、それらは私のコードをより短く、より理解しやすくしますか、それとも何ですか?
多くの人が回答を投稿しましたが、私のような馬鹿でも理解できる非常に単純なものについて、トランスデューサを使用した場合と使用しない場合の例を見るといいでしょう。もちろん、トランスデューサーが特定の高レベルの理解を必要とする場合を除いて、その場合、私はそれらを理解することは決してありません:(
トランスデューサーは、基になるシーケンスが何であるか (どのように行うか) を知らなくても、一連のデータをどう処理するかのレシピです。これは、任意の seq、async チャネル、または監視可能なものにすることができます。
それらは構成可能であり、ポリモーフィックです。
利点は、新しいデータ ソースが追加されるたびにすべての標準コンビネータを実装する必要がないことです。何回も何回も。その結果、ユーザーはこれらのレシピをさまざまなデータ ソースで再利用できます。
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
、またはすべての操作を単一の式に手動で構成する場合と比較して、抽象化の使用が容易になり、モジュール性が向上し、処理関数が再利用されます。
変換器は、機能を削減するための組み合わせの手段です。
例: リダクション関数は、これまでの結果と入力の 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
Rich Hickey は、Strange Loop 2014 カンファレンス (45 分) で「Transducers」の講演を行いました。
彼はトランスデューサとは何かを、空港でのバッグの処理という実際の例を使って簡単に説明しています。彼はさまざまな側面を明確に分離し、現在のアプローチと対比しています。最後に、彼は彼らの存在理由を説明します。
私は、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]
1xf
つには、Underscore を使用した通常の代替方法よりもずっときれいに見えます。
_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);
トランスデューサの明確な定義は次のとおりです。
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
さらに、サブグループも考慮に入れると非常に強力です。たとえば、Brown Family の子供の数を知りたい場合は、次のように実行できます。
;; 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 アリティ演算があります。