Clojure: ridurre vs. applicare


126

Comprendo la differenza concettuale tra reducee apply:

(reduce + (list 1 2 3 4 5))
; translates to: (+ (+ (+ (+ 1 2) 3) 4) 5)

(apply + (list 1 2 3 4 5))
; translates to: (+ 1 2 3 4 5)

Tuttavia, quale è il clojure più idiomatico? Fa molta differenza in un modo o nell'altro? Dal mio (limitato) test delle prestazioni, sembra che reducesia un po 'più veloce.

Risposte:


125

reducee applysono ovviamente solo equivalenti (in termini di risultato finale restituito) per le funzioni associative che devono vedere tutti i loro argomenti nel caso di variabili variabili. Quando sono equivalenti dal punto di vista del risultato, direi che applyè sempre perfettamente idiomatico, mentre reduceè equivalente - e potrebbe radere via una frazione di un battito di ciglia - in molti casi comuni. Ciò che segue è la mia logica per crederci.

+è di per sé implementato in termini di reducecaso di arità variabile (più di 2 argomenti). In effetti, questo sembra un modo "predefinito" immensamente sensato di andare per qualsiasi funzione associativa di arità variabile: reduceha il potenziale per eseguire alcune ottimizzazioni per accelerare le cose - forse attraverso qualcosa come internal-reduceuna novità 1.2 recentemente disabilitata nel master, ma speriamo di essere reintrodotto in futuro, che sarebbe sciocco replicare in ogni funzione che potrebbe trarne beneficio nel caso vararg. In tali casi comuni, applyaggiungerà solo un po 'di spese generali. (Nota che non c'è nulla di cui preoccuparsi davvero.)

D'altra parte, una funzione complessa potrebbe trarre vantaggio da alcune opportunità di ottimizzazione che non sono abbastanza generiche da integrare reduce; quindi applyti permetterebbe di trarne vantaggio mentre reducepotrebbe effettivamente rallentarti. Un buon esempio di quest'ultimo scenario che si verifica in pratica è fornito da str: utilizza StringBuilderinternamente e trarrà beneficio significativo dall'uso applypiuttosto che da reduce.

Quindi, direi l'uso in applycaso di dubbio; e se ti capita di sapere che non ti sta comprando nulla reduce(e che è improbabile che cambi molto presto), sentiti libero di usare reduceper radere quel piccolo sovraccarico inutile se ne hai voglia.


Bella risposta. In una nota suma margine , perché non includere una funzione integrata come in haskell? Sembra un'operazione abbastanza comune.
dbyrne,

17
Grazie, felice di sentirlo! Ri: sumDirei che Clojure ha questa funzione, si chiama +e puoi usarla con apply. :-) Scherzi a parte, penso in Lisp, in generale, se viene fornita una funzione variadica, di solito non è accompagnata da un wrapper che opera sulle raccolte - è quello che usi applyper (o reduce, se sai che ha più senso).
Michał Marczyk,

6
Divertente, il mio consiglio è l'opposto: in reducecaso di dubbio, applyquando sai per certo che c'è un'ottimizzazione. reduceIl contratto è più preciso e quindi più incline all'ottimizzazione generale. applyè più vago e quindi può essere ottimizzato solo caso per caso. stre concatsono i due esclusi principali.
cgrand il

1
@cgrand Una riformulazione della mia logica potrebbe essere approssimativamente quella per funzioni dove reducee applysono equivalenti in termini di risultati, mi aspetto che l'autore della funzione in questione sappia come ottimizzare il loro sovraccarico variadico e implementarlo in termini di reducese questo è davvero ciò che ha più senso (l'opzione per farlo è sicuramente sempre disponibile e rende un default eminentemente sensato). Vedo da dove vieni, tuttavia, reduceè decisamente centrale nella storia della performance di Clojure (e sempre di più), molto ottimizzata e chiaramente specificata.
Michał Marczyk,

51

Per i principianti che guardano questa risposta,
fai attenzione, non sono gli stessi:

(apply hash-map [:a 5 :b 6])
;= {:a 5, :b 6}
(reduce hash-map [:a 5 :b 6])
;= {{{:a 5} :b} 6}

21

Le opinioni variano - Nel grande mondo di Lisp, reduceè sicuramente considerato più idiomatico. In primo luogo, vi sono le questioni variadiche già discusse. Inoltre, alcuni compilatori Common Lisp falliranno effettivamente quando applyvengono applicati a liste molto lunghe a causa del modo in cui gestiscono le liste di argomenti.

Tra i clojuristi nella mia cerchia, tuttavia, l'utilizzo applyin questo caso sembra più comune. Trovo più facile grok e lo preferisco anche.


19

In questo caso non fa differenza, perché + è un caso speciale che può essere applicato a qualsiasi numero di argomenti. Ridurre è un modo per applicare una funzione che prevede un numero fisso di argomenti (2) a un elenco di argomenti arbitrariamente lungo.


9

Normalmente mi trovo a preferire il ridimensionare quando agisco su qualsiasi tipo di collezione - funziona bene ed è una funzione abbastanza utile in generale.

Il motivo principale che vorrei applicare è se i parametri significano cose diverse in posizioni diverse o se hai un paio di parametri iniziali ma vuoi ottenere il resto da una raccolta, ad es.

(apply + 1 2 other-number-list)

9

In questo caso specifico preferisco reduceperché è più leggibile : quando leggo

(reduce + some-numbers)

So subito che stai trasformando una sequenza in un valore.

Con applydevo considerare quale funzione viene applicata: "ah, è la +funzione, quindi sto ricevendo ... un singolo numero". Leggermente meno semplice.


7

Quando si utilizza una funzione semplice come +, in realtà non importa quale si utilizza.

In generale, l'idea è che si reducesta accumulando un'operazione. Presenti il ​​valore di accumulo corrente e un nuovo valore alla funzione di accumulo. Il risultato della funzione è il valore cumulativo per la successiva iterazione. Quindi, le tue iterazioni sembrano:

cum-val[i+1] = F( cum-val[i], input-val[i] )    ; please forgive the java-like syntax!

Per applicare, l'idea è che si sta tentando di chiamare una funzione in attesa di un numero di argomenti scalari, ma sono attualmente in una raccolta e devono essere estratti. Quindi, invece di dire:

vals = [ val1 val2 val3 ]
(some-fn (vals 0) (vals 1) (vals 2))

possiamo dire:

(apply some-fn vals)

ed è convertito in equivalente a:

(some-fn val1 val2 val3)

Quindi, usare "applica" è come "rimuovere le parentesi" attorno alla sequenza.


4

Un po 'tardi sull'argomento ma ho fatto un semplice esperimento dopo aver letto questo esempio. Ecco il risultato del mio sostituto, non riesco proprio a dedurre nulla dalla risposta, ma sembra che ci sia una sorta di kick nella cache tra ridurre e applicare.

user=> (time (reduce + (range 1e3)))
"Elapsed time: 5.543 msecs"
499500
user=> (time (apply + (range 1e3))) 
"Elapsed time: 5.263 msecs"
499500
user=> (time (apply + (range 1e4)))
"Elapsed time: 19.721 msecs"
49995000
user=> (time (reduce + (range 1e4)))
"Elapsed time: 1.409 msecs"
49995000
user=> (time (reduce + (range 1e5)))
"Elapsed time: 17.524 msecs"
4999950000
user=> (time (apply + (range 1e5)))
"Elapsed time: 11.548 msecs"
4999950000

Guardare il codice sorgente del clojure riduce la sua ricorsione abbastanza pulita con riduzione interna, tuttavia non ha trovato nulla sull'implementazione di applicare. L'implementazione di Clojure di + per applicare internoke invoke ridurre, che viene memorizzata nella cache da repl, che sembra spiegare la quarta chiamata. Qualcuno può chiarire cosa sta realmente accadendo qui?


So che preferirò ridurre ogni volta che posso :)
rohit

2
Non devi inserire la rangechiamata nel timemodulo. Mettilo all'esterno per rimuovere l'interferenza della costruzione della sequenza. Nel mio caso, reducesovraperforma costantemente apply.
Davyzhu,

3

Alla bellezza di apply viene data la funzione (+ in questo caso) può essere applicata all'elenco degli argomenti formato da argomenti intermedi pre-pendenti con una raccolta finale. Ridurre è un'astrazione per elaborare gli elementi di raccolta applicando la funzione per ciascuno e non funziona con il caso args variabile.

(apply + 1 2 3 [3 4])
=> 13
(reduce + 1 2 3 [3 4])
ArityException Wrong number of args (5) passed to: core/reduce  clojure.lang.AFn.throwArity (AFn.java:429)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.