Przetworniki to przepisy na to, co zrobić z sekwencją danych bez wiedzy, jaka jest podstawowa sekwencja (jak to zrobić). Może to być dowolny kanał sekwencyjny, asynchroniczny lub obserwowalny.
Są kompozycyjne i polimorficzne.
Zaletą jest to, że nie musisz implementować wszystkich standardowych kombinatorów za każdym razem, gdy dodawane jest nowe źródło danych. Znowu i znowu. W rezultacie jako użytkownik możesz ponownie wykorzystać te receptury w różnych źródłach danych.
Aktualizacja reklamy
We wcześniejszej wersji 1.7 Clojure istniały trzy sposoby pisania zapytań przepływu danych:
- połączenia zagnieżdżone
(reduce + (filter odd? (map #(+ 2 %) (range 0 10))))
- skład funkcjonalny
(def xform
(comp
(partial filter odd?)
(partial map #(+ 2 %))))
(reduce + (xform (range 0 10)))
- makro gwintowania
(defn xform [xs]
(->> xs
(map #(+ 2 %))
(filter odd?)))
(reduce + (xform (range 0 10)))
Przy przetwornikach napiszesz to tak:
(def xform
(comp
(map #(+ 2 %))
(filter odd?)))
(transduce xform + (range 0 10))
Wszyscy robią to samo. Różnica polega na tym, że nigdy nie wywołujesz bezpośrednio przetworników, tylko przekazujesz je do innej funkcji. Przetworniki wiedzą, co robić, funkcja, którą otrzymuje przetwornik, wie jak. Kolejność kombinatorów jest taka, jak w przypadku makra gwintowania (kolejność naturalna). Teraz możesz ponownie wykorzystać xform
kanał:
(chan 1 xform)
Przetworniki zwiększają wydajność i umożliwiają pisanie wydajnego kodu w bardziej modułowy sposób.
To przyzwoity przebieg .
W porównaniu do tworzenia połączeń do starego
map
,filter
,reduce
itd. Można uzyskać lepszą wydajność, ponieważ nie trzeba budować kolekcje pośrednich pomiędzy każdym kroku, a wielokrotnie chodzić te zbiory.W porównaniu z
reducers
ręcznym komponowaniem wszystkich operacji w jedno wyrażenie, uzyskujesz łatwiejsze w użyciu abstrakcje, lepszą modułowość i ponowne wykorzystanie funkcji przetwarzania.źródło
map
/reduce
nie używa kolekcji pośrednich, ponieważ wszystkie z nich tworzą łańcuch iteratorów. Gdzie się mylę?map
ifilter
twórz kolekcje pośrednie po zagnieżdżeniu.Przetworniki są środkiem łączącym w celu ograniczenia funkcji.
Przykład: Funkcje redukujące to funkcje, które przyjmują dwa argumenty: dotychczasowy wynik i dane wejściowe. Zwracają nowy wynik (jak dotąd). Na przykład
+
: Mając dwa argumenty, możesz traktować pierwszy jako wynik, a drugi jako dane wejściowe.Przetwornik może teraz przejąć funkcję + i uczynić ją funkcją podwójnego plus (podwaja każde wejście przed dodaniem). Tak wyglądałby ten przetwornik (w większości podstawowych pojęć):
Dla ilustracji substytut
rfn
z+
zobaczyć, jak+
przekształca się dwa razy-plus:Więc
dałoby teraz 12.
Funkcje redukcyjne zwracane przez przetworniki są niezależne od tego, w jaki sposób kumulowany jest wynik, ponieważ gromadzą się one wraz z przekazaną im funkcją redukcyjną, nieświadomie w jaki sposób. Tutaj używamy
conj
zamiast+
.Conj
pobiera kolekcję i wartość i zwraca nową kolekcję z dołączoną wartością.dałoby [2 4 6]
Są również niezależne od rodzaju źródła danych wejściowych.
Wiele przetworników można łączyć w łańcuch (dający się połączyć w łańcuch) przepis na przekształcenie funkcji redukujących.
Aktualizacja: Ponieważ istnieje już oficjalna strona o tym, gorąco polecam ją przeczytać: http://clojure.org/transducers
źródło
double
itransduce
?Załóżmy, że chcesz użyć szeregu funkcji do przekształcenia strumienia danych. Powłoka systemu Unix pozwala robić tego typu rzeczy za pomocą operatora potoku, np
(Powyższe polecenie zlicza użytkowników, których nazwa użytkownika zawiera literę r wielką lub małą). Jest to realizowane jako zestaw procesów, z których każdy odczytuje dane wyjściowe z poprzednich procesów, więc istnieją cztery strumienie pośrednie. Można sobie wyobrazić inną implementację, która łączy pięć poleceń w jedno zagregowane polecenie, które odczytuje dane wejściowe i zapisuje dane wyjściowe dokładnie raz. Gdyby strumienie pośrednie były drogie, a skład tani, może to być dobry kompromis.
To samo dotyczy Clojure. Istnieje wiele sposobów wyrażenia potoku transformacji, ale w zależności od tego, jak to zrobisz, możesz skończyć z pośrednimi strumieniami przechodzącymi od jednej funkcji do drugiej. Jeśli masz dużo danych, szybsze będzie połączenie tych funkcji w jedną funkcję. Przetworniki ułatwiają to. Wcześniejsza innowacja Clojure, reduktory, również na to pozwalają, ale z pewnymi ograniczeniami. Przetworniki usuwają niektóre z tych ograniczeń.
Więc odpowiadając na twoje pytanie, przetworniki niekoniecznie spowodują, że twój kod będzie krótszy lub bardziej zrozumiały, ale twój kod prawdopodobnie też nie będzie dłuższy lub mniej zrozumiały, a jeśli pracujesz z dużą ilością danych, przetworniki mogą sprawić, że twój kod szybciej.
To całkiem niezły przegląd przetworników.
źródło
pmap
, co nie wydaje się przyciągać wystarczającej uwagi. Jeślimap
pingujesz kosztowną funkcję w sekwencji, równoległe wykonanie operacji jest tak proste, jak dodanie „p”. Nie musisz niczego zmieniać w swoim kodzie i jest już dostępny - nie alfa, ani beta. (Jeśli funkcja tworzy sekwencje pośrednie, to chyba przetworniki mogą być szybsze.)Rich Hickey wygłosił wykład „Transducers” na konferencji Strange Loop 2014 (45 min).
Wyjaśnia w prosty sposób, czym są przetworniki, na przykładach z prawdziwego świata - przetwarzanie worków na lotnisku. Wyraźnie rozdziela różne aspekty i zestawia je z obecnymi podejściami. Pod koniec podaje uzasadnienie ich istnienia.
Wideo: https://www.youtube.com/watch?v=6mTbuzafcII
źródło
Odkryłem, że czytanie przykładów z transducers-js pomaga mi zrozumieć je w konkretny sposób, w jaki sposób mogę ich używać w codziennym kodzie.
Na przykład rozważ ten przykład (zaczerpnięty z pliku README pod powyższym linkiem):
Po pierwsze, użycie
xf
wygląda o wiele bardziej czysto niż zwykła alternatywa dla Underscore.źródło
t.into([], t.comp(t.map(inc), t.filter(isEven)), [0,1,2,3,4])
Przetworniki to (w moim rozumieniu!) Funkcje, które przejmują jedną funkcję redukcyjną, a zwracają inną. Funkcja redukująca to taka, która
Na przykład:
W tym przypadku mój-przetwornik przyjmuje funkcję filtrowania sygnału wejściowego, którą stosuje do 0, to jeśli ta wartość jest parzysta? w pierwszym przypadku filtr przekazuje tę wartość do licznika, a następnie filtruje następną wartość. Zamiast najpierw filtrować, a następnie przekazywać wszystkie te wartości do licznika.
Tak samo jest w drugim przykładzie, który sprawdza po jednej wartości na raz i jeśli ta wartość jest mniejsza niż 3, to pozwala policzyć dodać 1.
źródło
Oto wyraźna definicja przetwornika:
Aby to zrozumieć, rozważmy następujący prosty przykład:
A co z tym, że chcemy wiedzieć, ile dzieci jest we wsi? Z łatwością przekonamy się o tym przy pomocy następującego reduktora:
Oto inny sposób, aby to zrobić:
Poza tym jest naprawdę potężny, jeśli wziąć pod uwagę podgrupy. Na przykład, jeśli chcielibyśmy wiedzieć, ile dzieci jest w rodzinie Brown, możemy wykonać:
Mam nadzieję, że te przykłady okażą się pomocne. Więcej znajdziesz tutaj
Mam nadzieję, że to pomoże.
Clemencio Morales Lucas.
źródło
Napisałem o tym na blogu z przykładem skryptu clojurescript, który wyjaśnia, w jaki sposób funkcje sekwencji są teraz rozszerzalne dzięki możliwości zastąpienia funkcji redukującej.
Taki jest sens przetworników, gdy to czytam. Jeśli myślisz o
cons
lubconj
operacji, która jest zakodowana w operacjach takichmap
,filter
itd., Funkcja redukcji był nieosiągalny.W przypadku przetworników funkcja redukcji jest odsprzęgnięta i mogę ją zastąpić, tak jak zrobiłem z natywną tablicą javascript
push
dzięki przetwornikom.filter
i przyjaciele mają nową operację 1-arową, która zwróci funkcję przetwornika, której możesz użyć do dostarczenia własnej funkcji redukcji.źródło
Oto mój (głównie) żargon i odpowiedź bez kodu.
Pomyśl o danych w dwojaki sposób: o strumieniu (wartościach, które pojawiają się w czasie, takie jak zdarzenia) lub strukturze (danych, które istnieją w określonym momencie, takich jak lista, wektor, tablica itp.).
Istnieją pewne operacje, które możesz chcieć wykonać na strumieniach lub strukturach. Jedną z takich operacji jest mapowanie. Funkcja mapowania może zwiększyć każdy element danych (zakładając, że jest to liczba) o 1 i miejmy nadzieję, że można sobie wyobrazić, jak można to zastosować do strumienia lub struktury.
Funkcja odwzorowująca to tylko jedna z klas funkcji, które są czasami nazywane „funkcjami redukcyjnymi”. Inną popularną funkcją redukującą jest filtr, który usuwa wartości pasujące do predykatu (np. Usuwa wszystkie wartości, które są parzyste).
Przetworniki umożliwiają „zawinięcie” sekwencji jednej lub większej liczby funkcji redukujących i utworzenie „pakietu” (który sam jest funkcją), który działa na obu strumieniach lub strukturach. Na przykład można „spakować” sekwencję funkcji redukcyjnych (np. Filtrować liczby parzyste, a następnie mapować otrzymane liczby, aby zwiększyć je o 1), a następnie użyć tego „pakietu” przetwornika w strumieniu lub strukturze wartości (lub obu) .
Więc co jest w tym specjalnego? Zazwyczaj funkcje redukcyjne nie mogą być efektywnie skomponowane do pracy zarówno na strumieniach, jak i strukturach.
Zatem korzyścią dla Ciebie jest to, że możesz wykorzystać swoją wiedzę na temat tych funkcji i zastosować je w większej liczbie przypadków użycia. Koszt jest taki, że musisz nauczyć się dodatkowej maszyny (np. Przetwornika), aby uzyskać tę dodatkową moc.
źródło
O ile rozumiem, są one jak bloki konstrukcyjne , oddzielone od implementacji wejścia i wyjścia. Po prostu określ operację.
Ponieważ implementacja operacji nie znajduje się w kodzie wejścia i nic nie jest wykonywane z wyjściem, przetworniki są niezwykle wielokrotnego użytku. Przypominają mi Flows w Akka Streams .
Jestem też nowy w przetwornikach, przepraszam za prawdopodobnie niejasną odpowiedź.
źródło
Uważam, że ten post daje bardziej widok z lotu ptaka na przetwornik.
https://medium.com/@roman01la/understanding-transducers-in-javascript-3500d3bd9624
źródło