2015-08-13 10 views
10

Z nowym clojure 1.7 postanowiłem zrozumieć, gdzie mogę używać przetworników. Rozumiem, jakie korzyści mogą dać, ale nie mogę znaleźć normalnych przykładów pisania niestandardowych przetworników z wyjaśnieniem.Zachowanie przetworników Clojure

OK, próbowałem sprawdzić, co się dzieje. Otworzyłem dokumentację clojure. I tam przykłady używają xf jako argumentu. Po pierwsze: co oznacza ten xf lub xfrom? To wszystko wyprodukowało przetwornik tożsamości.

(defn my-identity [xf] 
    (fn 
    ([] 
    (println "Arity 0.") 
    (xf)) 
    ([result] 
    (println "Arity 1: " result " = " (xf result)) 
    (xf result)) 
    ([result input] 
    (println "Arity 2: " result input " = " (xf result input)) 
    (xf result input)))) 

Nazwałam zmienne [result input] z przykładu dokumentacji. Myślałem, że to jest jak w funkcji zmniejszania, gdzie result jest zredukowaną częścią, a input jest nowym elementem kolekcji.

Kiedy otrzymałem (transduce my-identity + (range 5)) otrzymałem wynik 10, którego oczekiwałem. Potem czytałem o eduction, ale nie mogę zrozumieć, co to jest. Zresztą zrobiłem (eduction my-identity (range 5)) i otrzymała:

Arity 2: nil 0 = nil 
Arity 2: nil 1 = nil 
Arity 1: nil = nil 
(0 0 1 1) 

Każdy element został powielony dlatego wzywam xf w println oświadczeniu. Dlaczego dwukrotnie powielono każdy przedmiot? Dlaczego mam zero? Czy zawsze otrzymam zero podczas dokonywania edycji? Czy mogę przekazać na to zachowanie?

Zresztą zrobiłem

> (reduce + (eduction my-identity (range 5)) 
clojure.core.Eduction cannot be cast to clojure.lang.IReduce 

Ok, wynik jest Eduction że nie sprowadza się, ale drukowane jak liście. Dlaczego nie można go zredukować? Po wpisaniu (doc eduction) otrzymuję że

Returns a reducible/iterable application of the transducers 
to the items in coll. 

nie powinno (transduce xform f coll) i (reduce f (eduction xfrom coll)) być takie same?

zrobiłem

> (reduce + (sequence my-identity (range 5)) 
20 

Oczywiście dostałam 20 ponieważ duplikatów. Znowu pomyślałem, że powinno to być i (reduce f (sequence xfrom coll)) być zawsze równe co najmniej w tak małym przykładzie bez jakichkolwiek przetworników stanu. To głupie, że oni nie są, czy ja się mylę?

Ok, następnie próbowałem (type (sequence my-identity (range 5))) i dostać clojure.lang.LazySeq pomyślałem, że to jest leniwy, ale gdy próbowałem wziąć first elementu Clojure oblicza wszystkie sekwencję naraz.

Więc moje podsumowanie:

1) Co oznacza XF lub xform?

2) Dlaczego otrzymuję nil jako argument result podczas eduction lub sequence?

3) Czy mogę zawsze być pewien, że będzie to nil podczas eduction lub sequence?

4) Co to jest eduction i jaki jest idiomatyczny pomysł, że nie można go zredukować? A jeśli tak, to jak mogę to zmniejszyć?

5) Dlaczego dostaję efekty uboczne podczas sequence lub eduction?

6) Czy mogę tworzyć rzeczywiste, leniwe sekwencje z przetwornikami?

+0

1) [clojure.org/transducers](http://clojure.org/transducers) - „Przetwornik XF jest stosem transformacja ... "; tak więc xf jest przetwornikiem (lub kompsem xfs), który nie jest funkcją (f), więc jeden nazywa go 'xf' – birdspider

+0

1) edit: powinien był to sformułować jako" i odróżnić go od zwykłej funkcji (f) jeden nazywa to 'xf' ' – birdspider

+0

Masz tu bardzo interesujące pytania, ale myślę, że uzyskasz więcej lepszych odpowiedzi, jeśli wydasz na nie mniejsze pytania. Pytanie "1" może być oddzielnym pytaniem ("Jakie jest znaczenie xf lub xform w kontekście przetworników?"), Nie wymaga nawet przykładu, to samo dotyczy pytań 4 i 6. – nberger

Odpowiedz

20

Wiele pytań, niech najpierw zacząć od kilku anwers:

  1. Tak, xf == xform jest "przetwornik".
  2. Twoja funkcja my-identity nie jest kompilowana. Masz parametr, a następnie wiele innych arities tej funkcji. Wierzę, że zapomniałeś o (fn ...).
  3. Twój argument do przetwornika tożsamości nazywa się xf. Jednak jest to zwykle nazywane rf, co oznacza "funkcję redukującą". Teraz myląca część to , która redukuje również funkcje xf (dlatego właśnie działa comp). Jednak jest mylące, że nazwałbyś go xf i powinieneś go nazwać rf.

  4. Przetworniki są zwykle "skonstruowane", ponieważ mogą być stanowe i/lub mają parametry przekazane przez . W twoim przypadku nie musisz go budować, ponieważ jest prosty i nie ma stanu ani nawet parametru. Należy jednak pamiętać, że zwykle można zawijać funkcję w innej funkcji zwracania fn. Oznacza to, że musisz zadzwonić pod numer (my-identity) zamiast po prostu przekazać go jako my-identity. Ponownie, tutaj jest dobrze, tylko trochę niekonwencjonalne i prawdopodobnie mylące.

  5. Najpierw kontynuujmy i udawajmy, że twój przetwornik my-identity jest poprawny (nie jest, a później wyjaśnię, co się dzieje).

  6. eduction jest stosunkowo rzadko używany. Tworzy "proces". tj. możesz uruchomić go w kółko i zobaczyć wynik. Zasadniczo, po prostu , podobnie jak masz listy lub wektory, które trzymają twoje przedmioty, edukcja "zatrzyma" wynik zastosowania przetwornika. Zauważ, że aby wykonać cokolwiek, potrzebujesz jeszcze (rf (funkcja redukująca).

  7. Na początku myślę, że warto pomyśleć o funkcje redukcji jako conj (lub faktycznie conj!) lub w przypadku +.

  8. Twoje eduction drukuje elementy produkuje ponieważ realizuje Iterable która jest wywoływana przez println lub swojej REPL. Po prostu wypisuje każdy element , który dodasz do swojego przetwornika z wywołaniem arity 2.

  9. wezwanie do (reduce + (eduction my-identity (range 5))) nie działa od Eduction (obiekt budowany w eduction) tylko realizuje IReduceInit. IReduceInit jak sugeruje jego nazwa, wymaga początkowej wartości . A więc to zadziała: (reduce + 0 (eduction my-identity (range 5)))

  10. Teraz, jeśli uruchomisz powyższy kod reduce, sugeruję, że zobaczysz coś bardzo interesującego pod kątem . Drukuje 10. Nawet jeśli twoja edycja wcześniej wydrukowała (0 0 1 1 2 2 3 3 4 4) (co jeśli dodasz razem to 20). Co tu się dzieje?

  11. Jak wspomniano wcześniej, twój przetwornik ma wadę. To nie działa poprawnie. Problem polega na tym, że wywołujesz numer rf, a następnie wywołujesz go ponownie w swojej funkcji arity 2. W clojure, rzeczy nie są zmienne, chyba że w jakiś sposób są wewnętrznie zmienne w celu optymalizacji :). Problem polega na tym, że czasami clojure używa mutacji i otrzymujesz duplikaty , nawet jeśli nigdy nie uchwyciłeś poprawnie wyniku twojego pierwszego połączenia z (rf) w twojej funkcji arity 2 (jako argumentu dla twojego println).

Naprawmy ty funkcjonować ale pozostawić drugą rf połączenia tam:

(defn my-identity2 [rf] 
    (fn 
     ([] 
     (println "Arity 0.") 
     (rf)) 
     ([result] 
     {:post [(do (println "Arity 1 " %) true)] 
     :pre [(do (println "Arity 1 " result) true)]} 
     (rf result)) 
     ([result input] 
     {:post [(do (println "Arity 2 " %) true)] 
     :pre [(do (println "Arity 2 " result input) true)]} 
     (rf (rf result input) input)))) 

Uwaga:

  • I przemianowany xf do rf jak zauważył earier.
  • Teraz widzimy, że używasz wyniku z Tobą rf i przekazujesz go do drugiej rozmowy z rf. przetwornik Nie jest to przetwornik tożsamość ale podwaja każdy element

Należy ostrożnie:

(transduce my-identity + (range 5));; => 10 
(transduce my-identity2 + (range 5));; => 20 

(count (into '() my-identity (range 200)));; => 200 
(count (into [] my-identity (range 200)));; => 400 

(count (into '() my-identity2 (range 200)));; => 400 
(count (into [] my-identity2 (range 200)));; => 400 

(eduction my-identity (range 5));;=> (0 0 1 1 2 2 3 3 4 4) 
(eduction my-identity2 (range 5));;=> (0 0 1 1 2 2 3 3 4 4) 

(into '() my-identity (range 5));;=> (4 3 2 1 0) 
(into [] my-identity (range 5));;=> [0 0 1 1 2 2 3 3 4 4] 
(into '() my-identity2 (range 5));;=> (4 4 3 3 2 2 1 1 0 0) 


(reduce + 0 (eduction my-identity (range 5)));;=> 10 
(reduce + (sequence my-identity (range 5)));;=> 20 

(reduce + 0 (eduction my-identity2 (range 5)));;=> 20 
(reduce + (sequence my-identity2 (range 5)));;=> 20 

Aby aswer pytania:

  1. eduction naprawdę nie przechodzą nil jako Argument result, gdy jest on zmniejszony. Po wydrukowaniu wartość ta wynosi zero, co wywołuje interfejs Iterable .
  2. Urządzenie naprawdę pochodzi z TransformerIterator, która jest specjalną klasą utworzoną dla przetworników. Ta klasa jest również używana do sequence, jak zauważyłeś. W stanie dokumentów:

Otrzymane elementy sekwencji są stopniowo obliczane.Sekwencje będą zużywać dane wejściowe przyrostowo zgodnie z potrzebami iw pełni realizować operacje pośrednie . To zachowanie różni się od równoważnych operacji na leniwych sekwencjach .

Powodem które otrzymałeś nil jako result argumentu dlatego iterator ma wynikowy zbiór, który posiada elementy powtórzyć w ciągu tak daleko. Po prostu przechodzi przez każdy element. Żadne państwo nie jest akumulowane.

Widać funkcję redukujący, który jest używany przez TransformerIterator jak i wewnętrzna klasa tutaj:

https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/TransformerIterator.java

Wykonaj CTRL+f i wprowadź xf.invoke aby zobaczyć, jak nazywany jest coraz przetwornik.

Funkcja sequence naprawdę nie jest tak leniwy jak naprawdę leniwy sekwencji ale Myślę, że to wyjaśnia tę część ciebie pytanie:

Are Clojure transducers eager?

sequence prostu oblicza wyniki przetwornika przyrostowego. Nic innego, niż .

Wreszcie właściwa funkcja tożsamości z niektórych wypowiedzi Debug:

(defn my-identity-prop [xf] 
    (fn 
    ([] 
    (println "Arity 0.") 
    (xf)) 
    ([result] 
    (let [r (xf result)] 
     (println "my-identity(" result ") =" r) 
     r)) 
    ([result input] 
    (let [r (xf result input)] 
     (println "my-idenity(" result "," input ") =" r) 
     r)))) 
+1

Dziękuję za takie miła odpowiedź. Rozumiem teraz, co się tam dzieje. 2) Tak, zapomniałem (fn ...) tam, więc jeśli nie masz nic przeciwko, zmienię swój post, aby to naprawić. 3) Przyjąłem nazwę "xf" z przykładu deduplikacji http://clojure.org/transducers. Ale patrzę na źródło clojure.core i jest tam nazywane "rf", tak jak sugerujesz, i ma to więcej sensu. Nareszcie należy tu wspomnieć, że "sekwencja" obliczyć sekwencję za pomocą kawałków, to mój przypadek to 32. Dlatego nie widziałem tego w teście z (zakres 5). Wielkie dzięki. – JustAnotherCurious

+2

To jest doskonała odpowiedź. Żałuję jedynie, że mam tylko jeden głos do oddania. –