tutorial - Clojure:reducir vs.aplicar




example js (9)

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.


Answers

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}

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.


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.


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.


reduce y apply son, por supuesto, solo equivalentes (en términos del resultado final devuelto) para las funciones asociativas que necesitan ver todos sus argumentos en el caso de ariadia variable. Cuando son equivalentes a los resultados, yo diría que apply es siempre perfectamente idiomático, mientras que reduce es equivalente, y podría reducirse a una fracción de un abrir y cerrar de ojos, en muchos 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 como para incorporarlas a la reduce ; luego apply te permitiría aprovechar esos, mientras que reduce podría en realidad hacerte más lento. Un buen ejemplo de este último escenario que ocurre en la práctica lo proporciona str : utiliza un StringBuilder internamente y se beneficiará significativamente del uso de apply lugar de reduce .

Por lo tanto, yo diría que use apply cuando tenga dudas; y si usted sabe que no le está comprando nada en lugar de reduce (y que es poco probable que cambie muy pronto), puede usar reduce para reduce esos gastos indirectos innecesarios si así lo desea.


Normalmente me encuentro prefiriendo reducir cuando actúo en 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, por ejemplo

(apply + 1 2 other-number-list)

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í?


No está completamente seguro de si esto es lo que necesita, pero puede expresar fácilmente que una clase tiene un conjunto de propiedades mediante la creación de una estructura gráfica de hechos (consulte la lista de hechos de propiedades a continuación y la regla para encontrar las propiedades dentro de un conjunto).

Luego, para expresar la composición de ese conjunto de propiedades, necesita otro conjunto de hechos de composición y reglas que descubran las subpropiedades de la clase y, como resultado, de qué cosas pueden componerse.

He dado un ejemplo de código a continuación también para ayudar a explicar.

property(bird, red_robotic_bird).
property(red, red_robotic_bird).
property(robot, red_robotic_bird).
property(tasty, cake).
property(red_robotic_bird, macaw).

property(Property, Thing) :-
    property(PropertySet, Thing),
    property(Property, PropertySet).


composition(buttons, red_robotic_bird).
composition(cheese, red_robotic_bird).
composition(wire, red_robotic_bird).
composition(flour, cake).
composition(Material, Thing) :-
    property(Property, Thing),
    composition(Material, Property).

consultas de ejemplo

?- composition(Material, macaw).
Material = buttons ; 
Material = cheese ; 
Material = wire ; 
no

?- composition(buttons, Thing).
Thing = red_robotic_bird ;
Thing = macaw ;
no

?- composition(flour, macaw).
no

?- property(bird, macaw).
yes

?- property(bird, cake).
no

property(Property, macaw).
Property = red_robotic_bird ;
Property = bird ;
Property = red ;
Property = robot ;
no

Las reglas del prólogo en breve.

Las reglas son esencialmente hechos (por ejemplo, animal(cat). ) que están condicionados a que otras reglas o hechos sean ciertos. Una regla se compone de una cabeza y un cuerpo ( head :- body. ). Un cuerpo es una prueba lógica más comúnmente expresada en forma conjuntiva normal (A / \ B / \ C). El operador y en el prólogo es, el operador o es ; (pero su uso está desaconsejado en las reglas), y el período ( . ) denota el final de una regla o hecho.

Tenga en cuenta que si una regla o hecho posterior en el cuerpo falla, entonces el prólogo retrocederá y solicitará una respuesta alternativa de una regla o hecho anterior y luego volverá a intentarlo. Considere el ejemplo un tanto artificioso a continuación.

share_same_colour (FruitA, FruitB): - color (Color, FruitA), color (Color, FruitB).

Si ejecutamos la consulta share_same_colour(apple, strawberry). luego colour(Colour, apple). podría devolver el color como verde. Sin embargo, no hay fresas verdes, así que el prólogo retrocederá y preguntará qué otros colores entran las manzanas. La siguiente respuesta podría ser roja, sobre la cual la segunda declaración de color tendría éxito y toda la regla sería verdadera.





clojure