Sin embargo, ¿cuál es la forma más idiomática? ¿Hace mucha diferencia de una forma u otra? Según mis pruebas de rendimiento (limitadas), parece que reducees un poco más rápido.
reducey apply, por supuesto, solo son equivalentes (en términos del resultado final devuelto) para funciones asociativas que necesitan ver todos sus argumentos en el caso de aridad variable. Cuando son equivalentes en cuanto a resultados, diría que applysiempre es perfectamente idiomático, mientras que reducees equivalente, y podría reducir una fracción de un abrir y cerrar de ojos, en muchos de los casos comunes. Lo que sigue es mi justificación para creer esto.
+se implementa en términos de reducepara el caso de aridad variable (más de 2 argumentos). De hecho, esto parece una forma "predeterminada" inmensamente sensata para cualquier función asociativa de aridad variable: reducetiene el potencial de realizar algunas optimizaciones para acelerar las cosas, tal vez a través de algo como internal-reduce, una novedad 1.2 recientemente deshabilitada en master, pero es de esperar que se reintroduzca en el futuro, lo cual sería una tontería replicar en todas las funciones que podrían beneficiarse de ellas en el caso de vararg. En tales casos comunes, applysolo agregará un poco de sobrecarga. (Tenga en cuenta que no hay nada de qué preocuparse realmente).
Por otro lado, una función compleja podría aprovechar algunas oportunidades de optimización que no son lo suficientemente generales como para ser incorporadas reduce; entonces applyte permitiría aprovecharlos, mientras reduceque en realidad podría ralentizarte. Un buen ejemplo de este último escenario que ocurre en la práctica lo proporciona str: utiliza StringBuilderinternamente y se beneficiará significativamente del uso de en applylugar de reduce.
Entonces, yo diría usar applycuando tengas dudas; y si sabe que no le está comprando nada reduce(y que es poco probable que esto cambie muy pronto), siéntase libre de usar reducepara reducir esa diminuta sobrecarga innecesaria si lo desea.
Gran respuesta. En una nota al margen, ¿por qué no incluir una sumfunción incorporada como en Haskell? Parece una operación bastante común.
dbyrne 01 de
17
Gracias, feliz de escuchar eso! Re:, sumyo diría que Clojure tiene esta función, se llama +y puedes usarla apply. :-) Hablando en serio, creo que en Lisp, en general, si se proporciona una función variada, no suele ir acompañada de un contenedor que opere en colecciones, para eso es lo que usa apply(o reduce, si sabe que tiene más sentido).
Michał Marczyk 01 de
66
Curioso, mi consejo es todo lo contrario: cuando tenga reducedudas, applycuando sepa con certeza que hay una optimización. reduceEl contrato es más preciso y, por lo tanto, más propenso a la optimización general. applyes más vago y, por lo tanto, solo puede optimizarse caso por caso. stry concatson las dos excepciones prevalentes.
cgrand
1
@cgrand Una reformulación de mi justificación podría ser más o menos que para las funciones donde reducey applyson equivalentes en términos de resultados, esperaría que el autor de la función en cuestión sepa la mejor manera de optimizar su sobrecarga variable y simplemente implementarla en términos de reducesi de hecho, eso es lo que tiene más sentido (la opción para hacerlo está siempre disponible y es un valor predeterminado muy sensato). Sin embargo, veo de dónde vienes, reducees definitivamente central en la historia de rendimiento de Clojure (y cada vez más), muy altamente optimizado y muy claramente especificado.
Michał Marczyk
51
Para los novatos que miran esta respuesta,
tengan cuidado, no son lo mismo:
Las opiniones varían: en el gran mundo de Lisp, reducedefinitivamente se considera más idiomático. Primero, están los problemas variados ya discutidos. Además, algunos compiladores de Common Lisp realmente fallarán cuando applyse apliquen a listas muy largas debido a cómo manejan las listas de argumentos.
Sin embargo, entre los Clojurists en mi círculo, usar applyen este caso parece más común. Me resulta más fácil asimilar y también lo prefiero.
No hace la 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 un número fijo de argumentos (2) a una lista arbitrariamente larga de argumentos.
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 usaría aplicar 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, por ejemplo
Cuando se usa una función simple como +, realmente no importa cuál uses.
En general, la idea es que reducees una operación acumulativa. Presente el valor de acumulación actual y un nuevo valor para su función de acumulación. El resultado de la función es el valor acumulativo para la próxima 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 que espera una serie de argumentos escalares, pero actualmente se encuentran en una colección y deben retirarse. Entonces, en lugar de decir:
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 patada de almacenamiento en caché entre reducir y aplicar.
Mirando el código fuente de clojure reduce su recursión bastante limpia con internal-reduce, sin embargo, no encontré nada sobre la implementación de apply. La implementación de Clojure de + para aplicar internamente invocar reduce, que se almacena en caché por repl, que parecen explicar la cuarta llamada. ¿Alguien puede aclarar lo que realmente está sucediendo aquí?
No debe poner la rangellamada dentro del timeformulario. Colóquelo afuera para eliminar la interferencia de la construcción de la secuencia. En mi caso, reduceconsistentemente supera apply.
Davyzhu el
3
La belleza de la aplicación se le da función (+ en este caso) se puede aplicar a la lista de argumentos formada por argumentos intermedios pre-pendientes con una colección final. Reducir es una abstracción para procesar elementos de colección que aplican la función para cada uno y no funciona con argumentos variables.
(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)
sum
función incorporada como en Haskell? Parece una operación bastante común.sum
yo diría que Clojure tiene esta función, se llama+
y puedes usarlaapply
. :-) Hablando en serio, creo que en Lisp, en general, si se proporciona una función variada, no suele ir acompañada de un contenedor que opere en colecciones, para eso es lo que usaapply
(oreduce
, si sabe que tiene más sentido).reduce
dudas,apply
cuando sepa con certeza que hay una optimización.reduce
El contrato 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 puede optimizarse caso por caso.str
yconcat
son las dos excepciones prevalentes.reduce
yapply
son equivalentes en términos de resultados, esperaría que el autor de la función en cuestión sepa la mejor manera de optimizar su sobrecarga variable y simplemente implementarla en términos dereduce
si de hecho, eso es lo que tiene más sentido (la opción para hacerlo está siempre disponible y es un valor predeterminado muy sensato). Sin embargo, veo de dónde vienes,reduce
es definitivamente central en la historia de rendimiento de Clojure (y cada vez más), muy altamente optimizado y muy claramente especificado.Para los novatos que miran esta respuesta,
tengan cuidado, no son lo mismo:
fuente
Las opiniones varían: en el gran mundo de Lisp,
reduce
definitivamente se considera más idiomático. Primero, están los problemas variados ya discutidos. Además, algunos compiladores de Common Lisp realmente fallarán cuandoapply
se apliquen a listas muy largas debido a cómo manejan las listas de argumentos.Sin embargo, entre los Clojurists en mi círculo, usar
apply
en este caso parece más común. Me resulta más fácil asimilar y también lo prefiero.fuente
No hace la 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 un número fijo de argumentos (2) a una lista arbitrariamente larga de argumentos.
fuente
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 usaría aplicar 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, por ejemplo
fuente
En este caso específico prefiero
reduce
porque es más legible : cuando leoSé 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". Ligeramente menos directo.fuente
Cuando se usa una función simple como +, realmente no importa cuál uses.
En general, la idea es que
reduce
es una operación acumulativa. Presente el valor de acumulación actual y un nuevo valor para su función de acumulación. El resultado de la función es el valor acumulativo para la próxima iteración. Entonces, sus iteraciones se ven así:Para aplicar, la idea es que está intentando llamar a una función que espera una serie de argumentos escalares, pero actualmente se encuentran en una colección y deben retirarse. Entonces, en lugar de decir:
podemos decir:
y se convierte para ser equivalente a:
Entonces, usar "aplicar" es como "quitar los paréntesis" alrededor de la secuencia.
fuente
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 patada de almacenamiento en caché entre reducir y aplicar.
Mirando el código fuente de clojure reduce su recursión bastante limpia con internal-reduce, sin embargo, no encontré nada sobre la implementación de apply. La implementación de Clojure de + para aplicar internamente invocar reduce, que se almacena en caché por repl, que parecen explicar la cuarta llamada. ¿Alguien puede aclarar lo que realmente está sucediendo aquí?
fuente
range
llamada dentro deltime
formulario. Colóquelo afuera para eliminar la interferencia de la construcción de la secuencia. En mi caso,reduce
consistentemente superaapply
.La belleza de la aplicación se le da función (+ en este caso) se puede aplicar a la lista de argumentos formada por argumentos intermedios pre-pendientes con una colección final. Reducir es una abstracción para procesar elementos de colección que aplican la función para cada uno y no funciona con argumentos variables.
fuente