2015-08-13 15 views
10

Con il nuovo clojure 1.7 ho deciso di capire dove posso usare i trasduttori. Capisco quali benefici possono dare, ma non riesco a trovare esempi normali di scrittura di trasduttori personalizzati con spiegazione.Comportamento dei trasduttori Clojure

Ok, ho provato a testare cosa sta succedendo. Ho aperto la documentazione del clojure. E gli esempi usano xf come argomento. Primo: cosa significa questo xf o xfrom? Questa roba ha prodotto un trasduttore di identità.

(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)))) 

ho preso la denominazione di variabili [result input] da esempio documentazione. Ho pensato che fosse come nella funzione di riduzione dove la parte result è ridotta e input è un nuovo elemento di raccolta.

Così quando faccio (transduce my-identity + (range 5)) ho ottenuto il risultato 10 quello che mi aspettavo. Poi ho letto su eduction, ma non riesco a capire di cosa si tratta. Comunque ho fatto (eduction my-identity (range 5)) ed ho ottenuto:

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

Ogni articolo ha ottenuto duplicato perché mi chiamo xf in println dichiarazione. Perché duplicato ogni elemento due volte? Perché mi sono perso? Riceverò sempre zero mentre faccio un'eduzione? Posso inoltrare questo comportamento?

Comunque ho fatto

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

Ok, il risultato è un Eduction che non è riducibile, ma stampato come una lista. Perché non è riducibile? Quando digito (doc eduction) Ottengo che

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

Non dovrebbe (transduce xform f coll) e (reduce f (eduction xfrom coll)) più lo stesso?

ho fatto

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

Naturalmente ho avuto 20 a causa di duplicati. Ancora una volta ho pensato che dovrebbe essere che (transduce xform f coll) e (reduce f (sequence xfrom coll)) essere sempre uguale almeno in un esempio così piccolo senza alcun trasduttore stateful. Questo è stupido che non lo siano, o mi sbaglio?

Ok, allora ho provato (type (sequence my-identity (range 5))) e ottenere clojure.lang.LazySeq ho pensato, che è pigro, ma quando ho provato a prendere il first elemento clojure calcolato tutta la sequenza in una sola volta.

Quindi la mia sintesi:

1) Cosa significa XF o XForm?

2) Perché ricevo nil come argomento result mentre eduction o sequence?

3) Potrei essere sempre sicuro che sarà nil mentre eduction o sequence?

4) Che cos'è eduction e qual è l'idea idiomatica non è riducibile? O se lo è, allora come posso ridurlo?

5) Perché ottengo effetti collaterali mentre sequence o eduction?

6) Posso creare sequenze pigro effettive con trasduttori?

+0

1) [clojure.org/transducers](http://clojure.org/transducers) - "La XF trasduttore è una pila di trasformazione ... "; quindi, xf è un trasduttore (o una comp di xfs) che è _not_ una funzione (f) quindi lo si chiama 'xf' – birdspider

+0

1) modifica: dovrebbe averlo formulato come 'e per distinguerlo da una funzione regolare (f) uno lo chiama 'xf' ' – birdspider

+0

Hai domande molto interessanti qui, ma penso che otterrai risposte sempre migliori se estrai domande più piccole da esso. Q "1" potrebbe essere una domanda separata ("Qual è il significato di xf o xform nel contesto dei trasduttori?"), Non ha nemmeno bisogno di un esempio, lo stesso vale per le domande 4 e 6. – nberger

risposta

20

Molte domande, si deve prima iniziare con alcuni anwers:

  1. Sì, xf == xform è un "trasduttore".
  2. La tua funzione my-identity non viene compilata. Si dispone di un parametro e quindi più altre entità della funzione. Credo che tu abbia dimenticato un (fn ...).
  3. L'argomento del trasduttore di identità è xf. Tuttavia, questo è chiamato solitamente rf, che significa "funzione di riduzione". Ora la parte confusa è che le funzioni xf riducono anche le funzioni (quindi comp funziona solo). Tuttavia, è confuso che lo chiami xf e dovresti chiamarlo rf.

  4. I trasduttori sono solitamente "costruiti" poiché possono essere in stato e/o sono parametri passati . Nel tuo caso, non è necessario costruirlo poiché è semplice e non ha stato né un parametro. Tuttavia, tieni presente che lo in genere avvolge la tua funzione in un'altra funzione di ritorno fn. Ciò significa che dovresti chiamare lo (my-identity) invece di inviarlo come my-identity al numero. Ancora, va bene qui, solo leggermente non convenzionale e probabilmente confuso.

  5. Prima continuiamo e facciamo finta che il tuo trasduttore my-identity sia corretto (non lo è, e ti spiegherò dopo cosa sta succedendo).

  6. eduction è utilizzato raramente. Crea un "processo". I.e. puoi eseguirlo più e più volte e vedere il risultato. Fondamentalmente, solo come se tu abbia elenchi o vettori che contengono i tuoi articoli, l'eduzione "manterrà" il risultato del trasduttore applicato. Si noti che per fare tutto ciò che si è necessario ancora un rf (funzione di riduzione).

  7. All'inizio penso che sia utile pensare di ridurre le funzioni come conj (o in realtà conj!) o nel tuo caso +.

  8. vostri eduction stampa gli elementi che produce in quanto implementa Iterable quale è invocato il println o il vostro REPL. Stampa semplicemente ogni elemento aggiunto al trasduttore con la chiamata arity 2.

  9. La chiamata al (reduce + (eduction my-identity (range 5))) non funziona in quanto Eduction (l'oggetto in costruzione in eduction) implementa solo IReduceInit. IReduceInit come suggerisce il nome fa richiede un valore iniziale . Quindi questo lavoro: (reduce + 0 (eduction my-identity (range 5)))

  10. Ora, se si esegue il sopra reduce come suggerisco vedrete qualcosa di molto interessante . Stampa 10. Anche se la tua eduzione è stata stampata in precedenza (0 0 1 1 2 2 3 3 4 4) (che se aggiungi insieme è 20). Cosa sta succedendo qui?

  11. Come indicato in precedenza, il trasduttore presenta un difetto. Non funziona correttamente. Il problema è che chiami il tuo rf e poi lo chiami di nuovo una seconda volta nella tua funzione di arity 2 . In clojure, roba non è mutabile, a meno che non sia in qualche modo mutabile internamente per scopi di ottimizzazione :). Qui il problema è che a volte il clojure utilizza la mutazione e ottieni duplicati anche se non acquisisci mai correttamente il risultato della prima volta che chiami (rf) nella tua funzione arity 2 (come argomento per il tuo println).

Diamo fix si funzione ma lasciare il secondo rf chiamata in là:

(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)))) 

Nota:

  • ho rinominato xf-rf come notato earier.
  • Ora possiamo vedere che si utilizza il risultato di voi rf e passarlo alla seconda chiamata di rf. Questo trasduttore non è un trasduttore di identità, ma raddoppia ogni elemento

osservare con attenzione:

(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 

Per risponderti le vostre domande:

  1. eduction in realtà non passare nil come il Argomento result quando è ridotto. Diventa solo nullo quando viene stampato che chiama l'interfaccia .
  2. Il nil proviene davvero da TransformerIterator che è una classe speciale creata per trasduttori. Questa classe viene anche utilizzata per sequence come hai notato. Come lo stato documenti:

Gli elementi di sequenza risultanti vengono calcolati in modo incrementale.Queste sequenze consumano l'input in modo incrementale secondo le necessità e realizzano completamente le operazioni intermedie . Questo comportamento si differenzia dalle operazioni equivalenti su sequenze lazy .

La ragione per cui si riceve nil come argomento result è perché un iteratore non ha insieme risultante che contiene gli elementi ripetuti su finora. Va semplicemente su ogni elemento. Nessuno stato viene accumulato.

si può vedere la funzione di riduzione che viene utilizzato dal TransformerIterator, come e classe interna qui:

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

fare un CTRL+f ed entrare xf.invoke per vedere come il vostro trasduttore è sempre chiamato.

La funzione sequence non è davvero così pigro come una sequenza veramente pigri, ma ho che questo spiega questa parte di te domanda:

Are Clojure transducers eager?

sequence calcola semplicemente i risultati di un trasduttore incrementale. Niente altro.

Infine, una funzione propria identità con alcune istruzioni di 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

Grazie per tale bella risposta Capisco ora cosa sta succedendo lì. 2) Sì, ho dimenticato (fn ...) lì, quindi se non ti dispiace, modifico il mio post per risolverlo. 3) Ho preso il nome "xf" dall'esempio http://clojure.org/transducers dedupe. Ma guardo la fonte di clojure.core e si chiama "rf" lì, come suggerisci tu, e ha più senso. Alla fine dovrei menzionare qui che "sequenza" calcola la sequenza in blocchi, nel mio caso è 32. Ecco perché non l'ho visto nel test con (range 5). Molte grazie. – JustAnotherCurious

+2

Questa è una risposta eccellente. Mi dispiace solo che ho solo un voto da dare. –

Problemi correlati