Bisakah seseorang menjelaskan Transduser Clojure kepada saya dengan istilah sederhana?

100

Saya telah mencoba membaca tentang ini tetapi saya masih tidak mengerti nilai mereka atau apa yang mereka gantikan. Dan apakah mereka membuat kode saya lebih pendek, lebih mudah dipahami, atau apa?

Memperbarui

Banyak orang memposting jawaban, tetapi alangkah baiknya melihat contoh dengan dan tanpa transduser untuk sesuatu yang sangat sederhana, yang bahkan orang idiot seperti saya pun dapat memahaminya. Kecuali, tentu saja, transduser memerlukan pemahaman tingkat tinggi tertentu, dalam hal ini saya tidak akan pernah memahaminya :(

appshare.co
sumber

Jawaban:

75

Transduser adalah resep yang harus dilakukan dengan urutan data tanpa mengetahui urutan yang mendasarinya (bagaimana melakukannya). Ini bisa berupa saluran seq, async, atau mungkin bisa diamati.

Mereka dapat disusun dan polimorfik.

Manfaatnya adalah, Anda tidak perlu menerapkan semua kombinator standar setiap kali sumber data baru ditambahkan. Lagi dan lagi. Akibatnya, Anda sebagai pengguna dapat menggunakan kembali resep tersebut di berbagai sumber data.

Pembaruan Iklan

Clojure versi 1.7 sebelumnya, Anda memiliki tiga cara untuk menulis kueri dataflow:

  1. panggilan bersarang
    (reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
  1. komposisi fungsional
    (def xform
      (comp
        (partial filter odd?)
        (partial map #(+ 2 %))))
    (reduce + (xform (range 0 10)))
  1. makro threading
    (defn xform [xs]
      (->> xs
           (map #(+ 2 %))
           (filter odd?)))
    (reduce + (xform (range 0 10)))

Dengan transduser Anda akan menulisnya seperti:

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

Mereka semua melakukan hal yang sama. Perbedaannya adalah Anda tidak pernah memanggil Transduser secara langsung, Anda meneruskannya ke fungsi lain. Transduser tahu apa yang harus dilakukan, fungsi yang membuat transduser tahu caranya. Urutan kombinator seperti Anda menulisnya dengan makro penguliran (urutan alami). Sekarang Anda dapat menggunakan kembali xformdengan saluran:

(chan 1 xform)
Aleš Roubíček
sumber
3
Saya lebih mencari jawaban yang dilengkapi dengan contoh yang menunjukkan kepada saya bagaimana transduser menghemat waktu saya.
appshare.co
Mereka tidak melakukannya jika Anda bukan Clojure atau beberapa pemelihara lib dataflow.
Aleš Roubíček
5
Ini bukan keputusan teknis. Kami hanya menggunakan keputusan berbasis nilai bisnis. "Gunakan saja" akan membuat saya dipecat
appshare.co
1
Anda mungkin lebih mudah mempertahankan pekerjaan jika Anda menunda mencoba menggunakan transduser hingga Clojure 1.7 dilepaskan.
pengguna100464
7
Transduser tampaknya menjadi cara yang berguna untuk mengabstraksi berbagai bentuk objek yang dapat berulang. Ini bisa jadi non-konsumsi, seperti Clojure seqs, atau konsumsi (seperti saluran async). Dalam hal ini, menurut saya Anda akan mendapatkan keuntungan besar dari penggunaan transduser jika, misalnya, Anda beralih dari implementasi berbasis seq ke implementasi core.async menggunakan saluran. Transduser harus memungkinkan Anda untuk menjaga inti logika Anda tidak berubah. Menggunakan pemrosesan berbasis urutan tradisional, Anda harus mengubahnya untuk menggunakan transduser atau analog inti-asinkron. Itu kasus bisnis.
Nathan Davis
47

Transduser meningkatkan efisiensi, dan memungkinkan Anda menulis kode yang efisien dengan cara yang lebih modular.

Ini adalah latihan yang layak .

Dibandingkan dengan menyusun panggilan ke yang lama map, filter, reducedll Anda mendapatkan kinerja yang lebih baik karena Anda tidak perlu membangun koleksi penengah antara setiap langkah, dan berulang kali berjalan koleksi mereka.

Dibandingkan dengan reducers, atau secara manual menyusun semua operasi Anda menjadi satu ekspresi, Anda akan lebih mudah menggunakan abstraksi, modularitas yang lebih baik, dan penggunaan kembali fungsi pemrosesan.

pembuat suara
sumber
2
Hanya ingin tahu, Anda mengatakan di atas: "untuk membangun koleksi perantara di antara setiap langkah". Tapi bukankah "koleksi perantara" terdengar seperti anti-pola? .NET menawarkan lazy enumerables, Java menawarkan lazy streams atau Guava-driven iterable, malas Haskell pasti memiliki sesuatu yang malas juga. Tak satu pun dari ini memerlukan map/ reducemenggunakan koleksi perantara karena semuanya membangun rantai iterator. Dimana saya salah disini?
Lyubomyr Shaydariv
3
Lakukan Clojure mapdan filterbuat koleksi perantara saat bersarang.
noisesmith
4
Dan setidaknya mengenai versi kemalasan Clojure, masalah kemalasan bersifat ortogonal di sini. Ya, map dan filter adalah lazy, juga menghasilkan container untuk nilai lazy saat Anda merantai keduanya. Jika Anda tidak memegang head, Anda tidak membuat urutan malas besar yang tidak diperlukan, tetapi Anda masih membangun abstraksi perantara tersebut untuk setiap elemen malas.
noisesmith
Sebuah contoh akan menyenangkan.
appshare.co
8
@LyubomyrShaydariv Dengan "koleksi perantara", noisesmith tidak berarti "mengulang / reifikasi seluruh koleksi, lalu mengulang / reifikasi seluruh koleksi lainnya". Maksudnya bahwa ketika Anda menumpuk panggilan fungsi yang mengembalikan urutan, setiap panggilan fungsi menghasilkan pembuatan urutan baru. Iterasi sebenarnya masih hanya terjadi sekali, tetapi terdapat konsumsi memori tambahan dan alokasi objek karena urutan bertingkat.
erikprice
22

Transduser adalah alat kombinasi untuk mengurangi fungsi.

Contoh: Fungsi pereduksi adalah fungsi yang mengambil dua argumen: Hasil sejauh ini dan masukan. Mereka mengembalikan hasil baru (sejauh ini). Misalnya +: Dengan dua argumen, Anda dapat menganggap yang pertama sebagai hasil sejauh ini dan yang kedua sebagai masukan.

Transduser sekarang dapat menggunakan fungsi + dan menjadikannya fungsi dua kali lebih banyak (menggandakan setiap input sebelum menambahkannya). Seperti inilah tampilan transduser itu (dalam istilah paling dasar):

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

Sebagai ilustrasi, gantikan rfndengan +untuk melihat bagaimana +diubah menjadi dua kali plus:

(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

Begitu

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

sekarang akan menghasilkan 12.

Fungsi pereduksi yang dikembalikan oleh transduser tidak bergantung pada bagaimana hasil diakumulasikan karena mereka terakumulasi dengan fungsi pereduksi yang diteruskan kepadanya, tanpa diketahui caranya. Di sini kami menggunakan conjsebagai pengganti +. Conjmengambil koleksi dan nilai dan mengembalikan koleksi baru dengan nilai yang ditambahkan.

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

akan menghasilkan [2 4 6]

Mereka juga tidak tergantung pada jenis sumber inputnya.

Beberapa transduser dapat dirangkai sebagai resep (dapat dirantai) untuk mengubah fungsi pengurangan.

Pembaruan: Karena sekarang ada halaman resmi tentang itu, saya sangat menyarankan untuk membacanya: http://clojure.org/transducers

Leon Grapenthin
sumber
Penjelasan yang bagus tetapi segera menjadi terlalu banyak jargon bagi saya, "Mengurangi fungsi yang dihasilkan oleh transduser tidak bergantung pada bagaimana hasil diakumulasikan".
appshare.co
1
Anda benar, kata yang dihasilkan tidak pantas di sini.
Leon Grapenthin
Tidak apa-apa. Bagaimanapun, saya mengerti bahwa Transformers hanyalah pengoptimalan sekarang, jadi mungkin sebaiknya tidak digunakan
appshare.co
1
Mereka adalah alat kombinasi untuk mengurangi fungsi. Di mana lagi kamu punya itu? Ini jauh lebih dari sekedar pengoptimalan.
Leon Grapenthin
Menurut saya jawaban ini sangat menarik, tetapi tidak jelas bagi saya bagaimana ini terhubung ke transduser (sebagian karena saya masih menganggap subjeknya membingungkan). Apa hubungan antara doubledan transduce?
Mars
21

Katakanlah Anda ingin menggunakan serangkaian fungsi untuk mengubah aliran data. Shell Unix memungkinkan Anda melakukan hal semacam ini dengan operator pipa, misalnya

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

(Perintah di atas menghitung jumlah pengguna dengan huruf r baik dalam huruf besar atau kecil pada nama pengguna mereka). Ini diimplementasikan sebagai sekumpulan proses, yang masing-masing membaca dari keluaran proses sebelumnya, jadi ada empat aliran perantara. Anda dapat membayangkan implementasi berbeda yang menyusun lima perintah menjadi satu perintah agregat, yang akan membaca dari masukannya dan menulis keluarannya tepat satu kali. Jika aliran perantara mahal, dan komposisinya murah, itu mungkin trade-off yang bagus.

Hal yang sama berlaku untuk Clojure. Ada beberapa cara untuk mengekspresikan pipeline transformasi, tetapi bergantung pada cara Anda melakukannya, Anda dapat berakhir dengan aliran perantara yang meneruskan dari satu fungsi ke fungsi berikutnya. Jika Anda memiliki banyak data, lebih cepat untuk menyusun fungsi-fungsi tersebut menjadi satu fungsi. Transduser memudahkan untuk melakukan itu. Inovasi Clojure sebelumnya, pereduksi, memungkinkan Anda melakukannya juga, tetapi dengan beberapa batasan. Transduser menghapus beberapa batasan tersebut.

Jadi untuk menjawab pertanyaan Anda, transduser tidak akan selalu membuat kode Anda lebih pendek atau lebih mudah dimengerti, tetapi kode Anda mungkin tidak akan lebih panjang atau kurang dapat dimengerti, dan jika Anda bekerja dengan banyak data, transduser dapat membuat kode Anda lebih cepat.

Ini adalah ikhtisar transduser yang cukup bagus.

pengguna100464
sumber
1
Ah, jadi transduser sebagian besar adalah pengoptimalan kinerja, apakah itu yang Anda maksud?
appshare.co
@ Zubair Ya, itu benar. Perhatikan bahwa pengoptimalan tidak hanya menghilangkan aliran perantara; Anda mungkin juga dapat melakukan operasi secara paralel.
pengguna100464
2
Perlu disebutkan pmap, yang sepertinya tidak mendapatkan cukup perhatian. Jika Anda melakukan mapping ke fungsi yang mahal melalui suatu urutan, membuat operasi paralel semudah menambahkan "p". Tidak perlu mengubah apa pun dalam kode Anda, dan itu tersedia sekarang - bukan alfa, bukan beta. (Jika fungsinya membuat urutan perantara, maka transduser mungkin lebih cepat, saya kira.)
Mars
10

Rich Hickey memberikan ceramah 'Transduser' di konferensi Strange Loop 2014 (45 menit).

Dia menjelaskan dengan cara sederhana apa itu transduser, dengan contoh dunia nyata - memproses tas di bandara. Dia dengan jelas memisahkan berbagai aspek dan membandingkannya dengan pendekatan saat ini. Menjelang akhir, dia memberikan alasan untuk keberadaan mereka.

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

Jordan Biserkov
sumber
8

Saya sudah menemukan membaca contoh-contoh dari transduser-js membantu saya memahami mereka secara konkret bagaimana saya bisa menggunakannya dalam kode sehari-hari.

Misalnya, pertimbangkan contoh ini (diambil dari README di tautan di atas):

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]

Pertama, menggunakan xfterlihat jauh lebih bersih daripada alternatif biasa dengan Underscore.

_.filter(_.map([0, 1, 2, 3, 4], inc), isEven);
enocom
sumber
Kenapa contoh transduser jauh lebih lama. Versi garis bawah terlihat jauh lebih ringkas
appshare.co
1
@ Zubair Tidak jugat.into([], t.comp(t.map(inc), t.filter(isEven)), [0,1,2,3,4])
Juan Castañeda
7

Transduser adalah fungsi (menurut pemahaman saya!) Yang mengambil satu fungsi reduksi dan mengembalikan yang lain. Fungsi pereduksi adalah salah satunya

Sebagai contoh:

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

Dalam hal ini transduser-saya mengambil fungsi pemfilteran input yang diterapkan ke 0 lalu jika nilainya genap? dalam kasus pertama filter meneruskan nilai itu ke penghitung, lalu memfilter nilai berikutnya. Alih-alih memfilter terlebih dahulu, lalu meneruskan semua nilai tersebut untuk dihitung.

Ini adalah hal yang sama pada contoh kedua ia memeriksa satu nilai pada satu waktu dan jika nilainya kurang dari 3 maka itu memungkinkan menghitung tambahkan 1.

Tobias
sumber
Saya menyukai penjelasan sederhana ini
Ignacio
7

Definisi transduser yang jelas ada di sini:

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.

Untuk memahaminya, mari kita perhatikan contoh sederhana berikut:

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

Bagaimana kalau kita ingin tahu ada berapa anak di desa itu? Kita dapat dengan mudah menemukannya dengan peredam berikut:

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

Berikut cara lain untuk melakukannya:

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

Selain itu, ini juga sangat berguna saat memperhitungkan subkelompok. Misalnya, jika kita ingin mengetahui berapa jumlah anak di Brown Family, kita dapat mengeksekusi:

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

Saya harap contoh-contoh ini bermanfaat bagi Anda. Anda dapat menemukan lebih banyak di sini

Semoga membantu.

Clemencio Morales Lucas.

Clemencio Morales Lucas
sumber
3
"Transduser adalah cara yang kuat dan dapat disusun untuk membangun transformasi algoritmik yang dapat Anda gunakan kembali dalam banyak konteks, dan mereka datang ke inti dan core.async Clojure." definisi dapat berlaku untuk hampir semua hal?
appshare.co
1
Bagi hampir semua Transduser Clojure, menurut saya.
Clemencio Morales Lucas
6
Ini lebih merupakan pernyataan misi daripada definisi.
Mars
4

Saya membuat blog tentang ini dengan contoh clojurescript yang menjelaskan bagaimana fungsi urutan sekarang dapat diperluas dengan dapat menggantikan fungsi reduksi.

Inilah inti dari transduser saat saya membacanya. Jika Anda memikirkan tentang operasi consatau conjyang memiliki kode keras dalam operasi seperti map, filterdll., Fungsi pengurangan tidak dapat dijangkau.

Dengan transduser, fungsi pereduksi dipisahkan dan saya dapat menggantinya seperti yang saya lakukan dengan array javascript asli pushberkat transduser.

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

filter dan teman-teman memiliki 1 operasi arity baru yang akan mengembalikan fungsi transduksi yang dapat Anda gunakan untuk memasok fungsi reduksi Anda sendiri.

dagda1
sumber
4

Inilah (kebanyakan) jargon dan kode jawaban gratis saya.

Pikirkan data dalam dua cara, aliran (nilai yang muncul dari waktu ke waktu seperti peristiwa) atau struktur (data yang ada pada suatu titik waktu seperti daftar, vektor, larik, dll).

Ada operasi tertentu yang mungkin ingin Anda lakukan pada aliran atau struktur. Salah satu operasi tersebut adalah pemetaan. Fungsi pemetaan mungkin menaikkan setiap item data (dengan asumsi itu adalah angka) sebesar 1 dan Anda diharapkan dapat membayangkan bagaimana hal ini dapat diterapkan pada aliran atau struktur.

Fungsi pemetaan hanyalah salah satu dari kelas fungsi yang terkadang disebut sebagai "fungsi pereduksi". Fungsi reduksi umum lainnya adalah filter yang menghapus nilai yang cocok dengan predikat (mis. Menghapus semua nilai yang genap).

Transduser memungkinkan Anda "membungkus" urutan satu atau lebih fungsi pereduksi dan menghasilkan "paket" (yang juga merupakan fungsi) yang bekerja pada aliran atau struktur. Misalnya, Anda dapat "mengemas" urutan fungsi pengurangan (mis. Memfilter angka genap, lalu memetakan angka yang dihasilkan untuk menambahnya dengan 1) dan kemudian menggunakan "paket" transduser tersebut pada aliran atau struktur nilai (atau keduanya) .

Jadi apa yang spesial dari ini? Biasanya, fungsi pereduksi tidak dapat disusun secara efisien untuk bekerja pada aliran dan struktur.

Jadi, manfaatnya bagi Anda adalah Anda dapat memanfaatkan pengetahuan Anda seputar fungsi-fungsi ini dan menerapkannya pada lebih banyak kasus penggunaan. Biaya yang harus Anda tanggung adalah Anda harus mempelajari beberapa mesin tambahan (misalnya transduser) untuk memberi Anda kekuatan ekstra ini.

optevo
sumber
2

Sejauh yang saya mengerti, mereka seperti blok bangunan , dipisahkan dari implementasi input dan output. Anda tinggal menentukan operasinya.

Karena implementasi operasi tidak ada dalam kode input dan tidak ada yang dilakukan dengan output, transduser sangat dapat digunakan kembali. Mereka mengingatkan saya pada Flow di Akka Streams .

Saya juga baru mengenal transduser, maaf atas jawaban yang mungkin tidak jelas.

kadir malak
sumber
1

Saya menemukan posting ini memberi Anda pandangan yang lebih luas tentang transduser.

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

ka yu Lai
sumber
3
Jawaban yang hanya mengandalkan tautan eksternal tidak disarankan pada SO karena tautan dapat rusak kapan saja di masa mendatang. Kutip konten dalam jawaban Anda.
Vincent Cantin
@VincentCantin Faktanya, postingan Medium telah dihapus.
Dmitri Zaitsev