2010-06-30 13 views
111

entiendo la diferencia conceptual entre reduce y apply:Clojure: reducir vs aplican

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

Sin embargo, ¿cuál es clojure más idiomática? ¿Hace mucha diferencia de una manera u otra? De mis pruebas de rendimiento (limitadas), parece que reduce es un poco más rápido.

Respuesta

107

reduce y apply son por supuesto, sólo equivalente (en términos del resultado final devuelto) para las funciones asociativas que tienen que ver todos sus argumentos en el caso-aridad variable. Cuando son equivalentes a los resultados, diría que apply es siempre perfectamente idiomático, mientras que reduce es equivalente, y podría reducir una fracción de un abrir y cerrar de ojos, en la mayoría de los casos comunes. Lo que sigue es mi razonamiento para creer esto.

+ se implementa en sí mismo en términos de reduce para el caso de aria variable (más de 2 argumentos). De hecho, esto parece una forma "predeterminada" muy sensible para cualquier función asociativa de aria variable: reduce tiene el potencial de realizar algunas optimizaciones para acelerar las cosas, quizás a través de algo como internal-reduce, una novedad 1.2 deshabilitada recientemente en el maestro , pero ojalá sea reintroducido en el futuro, lo cual sería una tontería repetir en cada función que podría beneficiarse de ellos en el caso vararg. En casos comunes, apply solo agregará un poco de sobrecarga. (Tenga en cuenta que no hay nada de qué preocuparse realmente).

Por otro lado, una función compleja puede aprovechar algunas oportunidades de optimización que no son lo suficientemente generales para integrarse en reduce; entonces apply le permitiría tomar ventaja de esos mientras que reduce en realidad podría hacerlo más lento. Un buen ejemplo del último escenario que ocurre en la práctica lo proporciona str: utiliza un StringBuilder internamente y se beneficiará significativamente del uso de apply en lugar de reduce.

Entonces, yo diría que use apply en caso de duda; y si usted sabe que no le está comprando nada más de reduce (y que es poco probable que esto cambie muy pronto), siéntase libre de usar reduce para reducir esa diminuta sobrecarga innecesaria si así lo desea.

+0

Gran respuesta. En una nota lateral, ¿por qué no incluir una función de suma incorporada como en Haskell? Parece una operación bastante común. – dbyrne

+14

¡Gracias, feliz de escuchar eso! Re: 'sum', yo diría que Clojure tiene esta función, se llama' + 'y puedes usarla con' apply'. :-) Hablando en serio, creo que en Lisp, en general, si se proporciona una función variadica, generalmente no va acompañada de un contenedor que opere en las colecciones, eso es lo que usas 'apply' para (o' reduce', si sabes eso tiene más sentido). –

+4

Es curioso, mi consejo es el opuesto: 'reduce' en caso de duda, 'aplica' cuando estés seguro de que hay una optimización. El contrato de 'reduce' es más preciso y, por lo tanto, más propenso a la optimización general. 'apply' es más vago y, por lo tanto, solo se puede optimizar caso por caso. 'str' y' concat' son las dos excepciones predominantes. – cgrand

13

No hace una diferencia en este caso, porque + es un caso especial que puede aplicarse a cualquier número de argumentos. Reducir es una forma de aplicar una función que espera una cantidad fija de argumentos (2) a una lista arbitrariamente larga de argumentos.

9

Normalmente me encuentro prefiriendo reducir cuando actúo sobre cualquier tipo de colección: funciona bien y es una función bastante útil en general.

La razón principal por la que utilizaría apply es si los parámetros significan cosas diferentes en diferentes posiciones, o si tiene un par de parámetros iniciales pero desea obtener el resto de una colección, p.

(apply + 1 2 other-number-list) 
18

Las opiniones varían- En el mundo de Lisp mayor, reduce definitivamente se considera más idiomático. Primero, están los problemas variados ya discutidos. Además, algunos compiladores Common Lisp fallarán cuando se aplique apply a listas muy largas debido a la forma en que manejan las listas de argumentos.

Entre los Clojuristas en mi círculo, sin embargo, usar apply en este caso parece más común. Me resulta más fácil grok y también lo prefiero.

41

Para los novatos que buscan en esta respuesta,
tenga cuidado, que no son los mismos:

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

excelente ejemplo, gracias! =) – sova

5

En este caso específico prefiero reduce porque es más legible: cuando leí

(reduce + some-numbers) 

Sé de inmediato que está convirtiendo una secuencia en un valor.

Tengo que considerar qué función se está aplicando: "ah, es la función +, así que estoy obteniendo ... un número único". Un poco menos directo.

4

Cuando se usa una función simple como +, realmente no importa cuál usar.

En general, la idea es reducir es una operación de acumulación. Usted presenta el valor de acumulación actual y un nuevo valor para su función de acumulación, con el resultado del valor acumulado para la siguiente iteración. Por lo tanto, sus iteraciones se parecen:

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

Para aplicar, la idea es que usted está tratando de llamar a una función que espera una serie de parámetros escalares, pero que se encuentran actualmente en una colección y necesitan ser retirado. Así, en lugar de decir:

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

podemos decir:

(apply some-fn vals) 

y se convierte a ser equivalente a:

(some-fn val1 val2 val3) 

Por lo tanto, el uso de "aplicar" es como "la eliminación el paréntesis "alrededor de la secuencia".

3

Mordí tarde en el tema pero hice un experimento simple después de leer este ejemplo. Aquí está el resultado de mi respuesta, simplemente no puedo deducir nada de la respuesta, pero parece que hay algún tipo de pacheo entre reducir y aplicar.

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 

mirar el código fuente de clojure reducir su recursividad bastante limpio con interna reducir, no fundó nada sobre la aplicación de aplicar sin embargo. La implementación de Clojure de + para aplicar internamente invoca reduce, que está almacenada en caché por repl, que parece explicar la cuarta llamada. ¿Alguien puede aclarar qué está pasando realmente aquí?

+0

Sé que preferiré reducir cada vez que pueda :) – rohit

+1

No debe poner la llamada 'range' dentro del formulario' time'. Póngalo afuera para eliminar la interferencia de la construcción de secuencia. En mi caso, 'reduce' consistentemente supera' apply'. – Davyzhu

Cuestiones relacionadas