Birisi bana Clojure Transducers'ı basit terimlerle açıklayabilir mi?


101

Bunu okumayı denedim ama hala onların değerini veya neyin yerini aldıklarını anlamıyorum. Kodumu daha kısa, daha anlaşılır mı yapıyorlar yoksa ne?

Güncelleme

Pek çok insan cevaplar yayınladı, ancak benim gibi bir aptalın bile anlayabileceği çok basit bir şey için dönüştürücü olan ve olmayan örnekleri görmek güzel olurdu. Elbette, dönüştürücüler belirli bir yüksek düzeyde anlayışa ihtiyaç duymadıkça, bu durumda onları asla anlamayacağım :(

Yanıtlar:


75

Dönüştürücüler, temel sıranın ne olduğu (nasıl yapılacağı) bilinmeden bir dizi veri ile ne yapılacağına dair tariflerdir. Herhangi bir sıralı, eşzamansız kanal veya gözlemlenebilir olabilir.

Oluşturulabilir ve polimorfiktirler.

Bunun faydası, her yeni veri kaynağı eklendiğinde tüm standart birleştiricileri uygulamanıza gerek olmamasıdır. Tekrar ve tekrar. Sonuç olarak, kullanıcı olarak bu tarifleri farklı veri kaynaklarında yeniden kullanabilirsiniz.

Reklam Güncelleme

Clojure'un 1.7 sürümünde, veri akışı sorguları yazmanın üç yolu vardı:

  1. iç içe aramalar
    (reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
  1. fonksiyonel kompozisyon
    (def xform
      (comp
        (partial filter odd?)
        (partial map #(+ 2 %))))
    (reduce + (xform (range 0 10)))
  1. iş parçacığı makrosu
    (defn xform [xs]
      (->> xs
           (map #(+ 2 %))
           (filter odd?)))
    (reduce + (xform (range 0 10)))

Dönüştürücülerle bunu şöyle yazacaksınız:

(def xform
  (comp
    (map #(+ 2 %))
    (filter odd?)))
(transduce xform + (range 0 10))

Hepsi aynı şeyi yapıyor. Aradaki fark, Transdüserleri asla doğrudan çağırmamanız, onları başka bir işleve aktarmanızdır. Dönüştürücüler ne yapacaklarını bilirler, dönüştürücüyü alan işlev nasıl yapılacağını bilir. Birleştiricilerin sırası, onu iş parçacığı makro (doğal düzen) ile yazdığınız gibidir. Artık xformkanalla yeniden kullanabilirsiniz :

(chan 1 xform)

3
Daha çok dönüştürücülerin bana nasıl zaman kazandırdığını gösteren bir örnekle gelen bir cevap arıyordum.
appshare.co

Clojure ya da dataflow lib bakıcısı değilseniz, yapmazlar.
Aleš Roubíček

5
Teknik bir karar değil. Yalnızca iş değerine dayalı kararları kullanırız. "Sadece onları kullan"
kovulmamı

1
Clojure 1.7 piyasaya sürülene kadar dönüştürücü kullanmayı geciktirirseniz, işinizi korumak daha kolay olabilir.
user100464

7
Dönüştürücüler, yinelenebilir nesnelerin çeşitli biçimleri üzerinde soyutlamanın yararlı bir yolu gibi görünmektedir. Bunlar Clojure seqs gibi sarf malzemesi veya sarf malzemesi (eşzamansız kanallar gibi) olabilir. Bu bakımdan, bana öyle geliyor ki, örneğin sıralı bir uygulamadan kanalları kullanarak bir core.async uygulamasına geçerseniz, dönüştürücüleri kullanmaktan büyük fayda sağlayacaksınız. Dönüştürücüler, mantığınızın özünü değiştirmeden tutmanıza izin vermelidir. Geleneksel sıra tabanlı işlemeyi kullanarak, bunu dönüştürücüleri veya bazı çekirdek-eşzamansız analogları kullanmak için dönüştürmeniz gerekir. İş vakası bu.
Nathan Davis

47

Dönüştürücüler verimliliği artırır ve daha modüler bir şekilde verimli kod yazmanıza olanak tanır.

Bu iyi bir deneme .

Eski çağrıları beste ile karşılaştırıldığında map, filter, reduceo koleksiyonları her adımda arasındaki ara koleksiyonları oluşturmak ve tekrar tekrar yürümek gerek yoktur çünkü vb daha iyi performans elde.

reducersTüm işlemlerinizi tek bir ifadede manuel olarak oluşturmaya kıyasla veya tek bir ifadede oluşturmaya kıyasla , soyutlamaları, daha iyi modülerliği ve işleme işlevlerinin yeniden kullanımını kolaylaştırırsınız.


2
Merak ediyorum, yukarıda "her adım arasında ara koleksiyonlar oluşturmak için" dediniz. Ama "ara koleksiyonlar" bir anti-model gibi gelmiyor mu? . Bunların hiçbiri ara koleksiyonları kullanmamayı map/ reducekullanmayı gerektirmez çünkü hepsi bir yineleyici zinciri oluşturur. Burada nerede yanıldım?
Lyubomyr Shaydariv

3
Perdeleyin mapve filteriç içe geçtiğinde ara koleksiyonlar oluşturun.
gürültü ustası

4
Ve en azından Clojure'un tembellik versiyonuna gelince, burada tembellik meselesi ortogonaldir. Evet, harita ve filtre tembeldir, ayrıca bunları zincirlediğinizde tembel değerler için kapsayıcılar oluşturur. Kafayı tutmazsanız, ihtiyaç duyulmayan büyük tembel diziler oluşturmazsınız, ancak yine de her tembel öğe için bu ara soyutlamaları oluşturursunuz.
gürültü ustası

Bir örnek iyi olur.
appshare.co

8
@LyubomyrShaydariv "Ara koleksiyon" ile, gürültü ustası "tüm koleksiyonu yineleyin / yeniden adlandırın, ardından tüm koleksiyonu yineleyin / yeniden biçimlendirin" anlamına gelmez. Sıralı döndüren işlev çağrılarını iç içe yerleştirdiğinizde, her işlev çağrısının yeni bir sıralı oluşturma ile sonuçlandığı anlamına gelir. Gerçek yineleme hala yalnızca bir kez gerçekleşir, ancak iç içe geçmiş sıralar nedeniyle ek bellek tüketimi ve nesne tahsisi vardır.
erikprice

22

Dönüştürücüler, fonksiyonları azaltmak için bir kombinasyon yoludur.

Örnek: Azaltma işlevleri, iki bağımsız değişken alan işlevlerdir: Şimdiye kadarki bir sonuç ve bir girdi. Yeni bir sonuç döndürürler (şimdiye kadar). Örneğin +: İki bağımsız değişkenle, ilkini şimdiye kadarki sonuç, ikincisini girdi olarak düşünebilirsiniz.

Bir dönüştürücü artık + işlevini alabilir ve bunu iki artı işlevi yapabilir (eklemeden önce her girişi ikiye katlar). Bu dönüştürücü şu şekilde görünecektir (çoğu temel anlamda):

(defn double
  [rfn]
  (fn [r i] 
    (rfn r (* 2 i))))

İkili artıya nasıl dönüştürüldüğünü görmek için rfnile ikame etmek +için +:

(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

Yani

(reduce (double +) 0 [1 2 3]) 

şimdi 12 verir.

Dönüştürücüler tarafından döndürülen indirgeme işlevleri, sonucun nasıl biriktirildiğinden bağımsızdır çünkü bilmeden kendilerine aktarılan indirgeme işlevi ile birikirler. Burada conjyerine kullanıyoruz +. Conjbir koleksiyon ve bir değer alır ve bu değerin eklendiği yeni bir koleksiyon döndürür.

(reduce (double conj) [] [1 2 3]) 

sonuç verirdi [2 4 6]

Ayrıca, girdinin ne tür bir kaynak olduğundan bağımsızdırlar.

İndirgeme işlevlerini dönüştürmek için (zincirlenebilir) bir reçete olarak birden fazla dönüştürücü zincirlenebilir.

Güncelleme: Şu anda bununla ilgili resmi bir sayfa olduğundan, onu okumanızı şiddetle tavsiye ediyorum: http://clojure.org/transducers


Güzel açıklama, ancak kısa sürede benim için çok fazla jargona girdim, "Dönüştürücüler tarafından üretilen azaltma işlevleri, sonucun nasıl toplandığından bağımsızdır".
appshare.co

1
Haklısın, burada üretilen kelime uygunsuzdu.
Leon Grapenthin

Tamam. Her neyse, Transformers'ın artık sadece bir optimizasyon olduğunu anlıyorum, bu yüzden muhtemelen kullanılmamalı
appshare.co

1
Fonksiyonları azaltmak için bir kombinasyon aracıdırlar. Başka nerede var Bu bir optimizasyondan çok daha fazlasıdır.
Leon Grapenthin

Bu cevabı çok ilginç buluyorum, ancak dönüştürücülere nasıl bağlandığı bana net değil (kısmen konuyu hala kafa karıştırıcı bulduğum için). Arasındaki ilişki nedir doubleve transduce?
Mars

22

Bir veri akışını dönüştürmek için bir dizi işlev kullanmak istediğinizi varsayalım. Unix kabuğu, boru operatörü ile bu tür şeyler yapmanızı sağlar, örn.

cat /etc/passwd | tr '[:lower:]' '[:upper:]' | cut -d: -f1| grep R| wc -l

(Yukarıdaki komut, kullanıcı adlarında büyük veya küçük harfli r harfi bulunan kullanıcıların sayısını sayar). Bu, her biri önceki işlemlerin çıktılarını okuyan bir dizi işlem olarak uygulanır, bu nedenle dört ara akış vardır. Beş komutu tek bir toplama komutunda oluşturan farklı bir uygulama hayal edebilirsiniz, bu komut girdisinden okuyup çıktısını tam olarak bir kez yazacaktır. Ara akışlar pahalıysa ve kompozisyon ucuzsa, bu iyi bir değiş tokuş olabilir.

Aynı şey Clojure için de geçerli. Bir dönüşüm hattını ifade etmenin birden fazla yolu vardır, ancak bunu nasıl yaptığınıza bağlı olarak, bir işlevden diğerine geçen ara akışlarla sonuçlanabilirsiniz. Çok fazla veriniz varsa, bu işlevleri tek bir işlevde oluşturmak daha hızlıdır. Dönüştürücüler bunu yapmayı kolaylaştırır. Daha eski bir Clojure yeniliği olan azaltıcılar, bunu da yapmanıza izin verir, ancak bazı kısıtlamalarla. Dönüştürücüler bu kısıtlamalardan bazılarını kaldırır.

Dolayısıyla, sorunuzu yanıtlamak için, dönüştürücüler kodunuzu daha kısa veya daha anlaşılır hale getirmeyecektir, ancak kodunuz muhtemelen daha uzun veya daha az anlaşılır olmayacaktır ve çok fazla veriyle çalışıyorsanız, dönüştürücüler kodunuzu yapabilir Daha hızlı.

Bu , dönüştürücülerin oldukça iyi bir özetidir.


1
Ah, yani dönüştürücüler çoğunlukla performans optimizasyonudur, bunu mu söylüyorsunuz?
appshare.co

@Zubair Evet, bu doğru. Optimizasyonun ara akışları ortadan kaldırmanın ötesine geçtiğini unutmayın; paralel olarak da işlemler gerçekleştirebilirsiniz.
user100464

2
pmapYeterince dikkat çekmeyen, bahsetmeye değer . Eğer varsa mapoperasyon paralel yapan bir sekans boyunca pahalı bir fonksiyon ping "p" eklemek kadar kolaydır. Kodunuzda başka hiçbir şeyi değiştirmenize gerek yok ve şu anda kullanılabilir - alfa değil, beta değil. (İşlev ara diziler oluşturuyorsa, dönüştürücüler daha hızlı olabilir, tahmin ediyorum.)
Mars

10

Rich Hickey, Strange Loop 2014 konferansında (45 dakika) bir 'Transducers' konuşması yaptı.

Transdüserlerin ne olduğunu gerçek dünyadan örneklerle basit bir şekilde açıklıyor - bir havalimanında çantaların işlenmesi. Farklı yönleri net bir şekilde ayırır ve bunları mevcut yaklaşımlarla karşılaştırır. Sonlara doğru, onların varlığının gerekçesini veriyor.

Video: https://www.youtube.com/watch?v=6mTbuzafcII


8

Transdüserler-js'den örnekler okumak, onları günlük kodda nasıl kullanabileceğime dair somut terimlerle anlamama yardımcı oluyor.

Örneğin, şu örneği düşünün (yukarıdaki bağlantıdaki BENİOKU'dan alınmıştır):

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]

Birincisi, kullanmak xfUnderscore ile olağan alternatiften çok daha temiz görünüyor.

_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);

Dönüştürücüler örneği nasıl bu kadar uzun oluyor? Alt çizgi versiyonu çok daha kısa görünüyor
appshare.co

1
@Zubair Değil gerçektent.into([], t.comp(t.map(inc), t.filter(isEven)), [0,1,2,3,4])
Juan Castañeda

7

Dönüştürücüler (anladığım kadarıyla) bir indirgeme işlevi alıp diğerini döndüren işlevlerdir. İndirgeme işlevi,

Örneğin:

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

Bu durumda, transdüser, 0 için geçerli olan bir giriş filtreleme işlevi alır, o zaman bu değer çift ise? ilk durumda filtre bu değeri sayaca iletir, ardından sonraki değeri filtreler. Önce filtrelemek ve ardından tüm bu değerleri saymak için geçirmek yerine.

İkinci örnekte de aynı şey, her seferinde bir değeri kontrol ediyor ve bu değer 3'ten küçükse saymaya 1 eklemeye izin veriyor.


Bu basit açıklamayı beğendim
Ignacio

7

Bir dönüştürücü net tanımı burada:

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.

Bunu anlamak için aşağıdaki basit örneği ele alalım:

;; 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}])

Köyde kaç çocuk olduğunu bilmek ister miyiz? Aşağıdaki redüktör ile kolayca bulabiliriz:

;; 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

İşte bunu yapmanın başka bir yolu:

;; 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

Ayrıca, alt grupları hesaba katarken de gerçekten güçlüdür. Örneğin, Brown Ailesi'nde kaç çocuk olduğunu bilmek istersek, şunları yapabiliriz:

;; 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

Umarım bu örnekleri faydalı bulursunuz. Daha fazlasını burada bulabilirsiniz

Umarım yardımcı olur.

Clemencio Morales Lucas.


3
"Dönüştürücüler, birçok bağlamda yeniden kullanabileceğiniz algoritmik dönüşümler oluşturmanın güçlü ve birleştirilebilir bir yoludur ve Clojure core ve core.async'e geliyorlar." tanım neredeyse her şeye uygulanabilir mi?
appshare.co

1
Neredeyse tüm Clojure Transducer'lar için diyebilirim.
Clemencio Morales Lucas

6
Bir tanımdan çok bir misyon ifadesidir.
Mars

4

İndirgeme işlevini değiştirerek sıralama işlevlerinin artık nasıl genişletilebilir olduğunu açıklayan bir clojurescript örneği ile bunun hakkında blog yazdım .

Bu benim okuduğum dönüştürücülerin amacı. consVeya hakkında düşünürsenizconj sert gibi operasyonlarda kodlanmıştır operasyonda map, filtervb azaltarak işlev ulaşılamaz oldu.

Dönüştürücülerle, indirgeme işlevi ayrıştırılır ve pushdönüştürücüler sayesinde yerel javascript dizisiyle yaptığım gibi onu değiştirebilirim .

(transduce (filter #(not (.hasOwnProperty prevChildMapping %))) (.-push #js[]) #js [] nextKeys)

filter ve arkadaşlarınız, kendi azaltma işlevinizi sağlamak için kullanabileceğiniz bir dönüştürme işlevi döndürecek yeni bir 1 uçlu operasyona sahiptir.


4

İşte benim (çoğunlukla) jargon ve kodsuz cevabım.

Verileri iki şekilde düşünün, bir akış (olaylar gibi zaman içinde oluşan değerler) veya bir yapı (liste, vektör, dizi vb. Gibi zaman içinde bir noktada var olan veriler).

Akışlar veya yapılar üzerinde gerçekleştirmek isteyebileceğiniz belirli işlemler vardır. Böyle bir işlem haritalandırmadır. Bir eşleme işlevi her bir veri öğesini 1 artırabilir (bunun bir sayı olduğu varsayılarak) ve bunun bir akışa veya bir yapıya nasıl uygulanabileceğini umarız hayal edebilirsiniz.

Bir eşleme işlevi, bazen "azaltma işlevleri" olarak adlandırılan bir işlev sınıfından yalnızca biridir. Diğer bir yaygın indirgeme işlevi, bir yüklemle eşleşen değerleri kaldıran filtredir (örneğin, çift olan tüm değerleri kaldırır).

Transdüserler, bir veya daha fazla indirgeme işlevi dizisini "sarmanıza" ve hem akışlar hem de yapılar üzerinde çalışan bir "paket" (bu da bir işlevdir) üretmenize izin verir. Örneğin, bir azaltma işlevi dizisini "paketleyebilir" (ör. Çift sayıları filtreleyebilir, ardından elde edilen sayıları 1 artıracak şekilde eşleyebilir) ve ardından bu dönüştürücü "paketini" bir akış veya değer yapısı (veya her ikisi) üzerinde kullanabilirsiniz. .

Peki bu konuda özel olan nedir? Tipik olarak, azaltma işlevleri hem akışlar hem de yapılar üzerinde çalışmak için verimli bir şekilde oluşturulamaz.

Bu yüzden size yararı, bilginizi bu işlevler etrafında kullanabilmeniz ve bunları daha fazla kullanım senaryosunda uygulayabilmenizdir. Size maliyeti, size bu ekstra gücü vermek için fazladan makine (yani dönüştürücü) öğrenmeniz gerektiğidir.


2

Anladığım kadarıyla , giriş ve çıkış uygulamasından ayrılmış yapı taşları gibiler . Siz sadece operasyonu tanımlarsınız.

İşlemin uygulanması giriş kodunda olmadığından ve çıkışla hiçbir şey yapılmadığından, transdüserler son derece yeniden kullanılabilir. Ait Onlar bana hatırlatmak Akış içinde s Akka Akımlar .

Ayrıca dönüştürücülerde yeniyim, muhtemelen belirsiz cevap için üzgünüm.


1

Bu yazının size dönüştürücünün daha kuş bakışı görünümünü verdiğini görüyorum.

https://medium.com/@roman01la/understanding-transducers-in-javascript-3500d3bd9624


3
SO'da yalnızca dış bağlantılara dayanan yanıtlar önerilmez, çünkü bağlantılar gelecekte herhangi bir zamanda kopabilir. Bunun yerine cevabınızdaki içeriği alıntı yapın.
Vincent Cantin

@VincentCantin Aslında, Medium gönderisi silindi.
Dmitri Zaitsev

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.