tutorial - Clojure: reducir vs. aplicar




clojurescript (6)

Entiendo la diferencia conceptual entre reduce y 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)

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


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

En general, la idea es que 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. Entonces, sus iteraciones se ven así:

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

Para aplicar, la idea es que está intentando llamar a una función esperando una cantidad de argumentos escalares, pero actualmente están en una colección y deben retirarse. Entonces, en lugar de decir:

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

podemos decir:

(apply some-fn vals)

y se convierte para ser equivalente a:

(some-fn val1 val2 val3)

Entonces, usar "aplicar" es como "eliminar los paréntesis" alrededor de la secuencia.


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

(reduce + some-numbers)

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

Con apply tengo que considerar qué función se está aplicando: "ah, es la función + , así que obtengo ... un solo número". Un poco menos directo.


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 de Common Lisp fallarán cuando apply se aplique a listas muy largas debido a la forma en que manejan las listas de argumentos.

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


No importa en este caso, porque + es un caso especial que puede aplicarse a cualquier cantidad 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.


Para los novatos que miran esta respuesta,
ten cuidado, no son lo mismo:

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

Un poco 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

Al observar el código fuente de clojure reducir su recursión bastante limpia con internal-reduce, no se encontró nada en la implementación de apply. 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í?





clojure