haskell - orden - programacion funcional currificacion
¿Uso práctico de las funciones al curry? (10)
Creo que el curry es una forma tradicional de manejar las funciones generales de n-aria siempre que las únicas que puedas definir sean únicas.
Por ejemplo, en el cálculo lambda (de donde provienen los lenguajes de programación funcional), solo hay abstracciones de una variable (que se traducen en funciones unarias en los FPL). Con respecto al cálculo lambda, creo que es más fácil probar cosas acerca de tal formalismo, ya que en realidad no es necesario manejar el caso de las funciones n-arias (ya que puede representar cualquier función n-aria con una cantidad de funciones únicas a través del curry) .
(Otros ya han cubierto algunas de las implicaciones prácticas de esta decisión, por lo que me detengo aquí).
Hay toneladas de tutoriales sobre cómo asignar funciones y muchas preguntas aquí en stackoverflow. Sin embargo, después de leer The Little Schemer, varios libros, tutoriales, publicaciones de blog y subprocesos de stackoverflow todavía no sé la respuesta a la pregunta simple: "¿Cuál es el punto de curry?" Entiendo cómo aplicar una función, pero no el "¿por qué?" Detrás de eso.
¿Podría alguien explicarme los usos prácticos de las funciones con curry (fuera de los idiomas que solo permiten un argumento por función, donde la necesidad de usar el curry es, por supuesto, bastante evidente)?
edición: teniendo en cuenta algunos ejemplos de TLS, ¿cuál es el beneficio de
(define (action kind)
(lambda (a b)
(kind a b)))
Opuesto a
(define (action kind a b)
(kind a b))
Solo puedo ver más código y ninguna flexibilidad adicional ...
En sí mismo el curry es azúcar sintáctico. El azúcar sintáctica tiene que ver con lo que quieres hacer fácil. C, por ejemplo, quiere hacer instrucciones que sean "baratas" en lenguaje ensamblador como incremental, fácil y, por lo tanto, tienen azúcar sintáctica para el incremento, la notación ++.
t = x + y
x = x + 1
se sustituye por t = x ++ + y
Los lenguajes funcionales podrían fácilmente tener cosas como.
f(x,y,z) = abc
g(r,s)(z) = f(r,s,z).
h(r)(s)(z) = f(r,s,z)
pero en cambio todo es automático. Y eso permite que un límite ag por un r0 particular, s0 (es decir, valores específicos) se pase como una función de una variable.
Tomemos, por ejemplo, la función de clasificación de Perl que toma la sub-lista de clasificación donde sub es una función de dos variables que se evalúa como un booleano y la lista es una lista arbitraria.
Naturalmente, desearía utilizar operadores de comparación (<=>) en Perl y tener sortordinal = sort (<=>) donde sortordinal funciona en las listas. Para hacer esto, se ordenaría ser una función al curry.
Y, de hecho, una especie de lista se define precisamente de esta manera en Perl.
En resumen: el curry es azúcar para hacer que las funciones de primera clase sean más naturales.
Es muy fácil crear cierres. De vez en cuando uso SRFI-26. Es realmente lindo
Me gustaría añadir un ejemplo a la respuesta de @Francesco.
No podemos componer directamente funciones que tengan múltiples parámetros. Dado que la composición de funciones es uno de los conceptos clave en la programación funcional. Usando la técnica de Currying podemos componer funciones que toman múltiples parámetros.
Por lo tanto, no tienes que aumentar la plantilla con un poco de lambda.
Pueden hacer que el código sea más fácil de leer. Considere los siguientes dos fragmentos de Haskell:
lengths :: [[a]] -> [Int]
lengths xs = map length xs
lengths'' :: [[a]] -> [Int]
lengths'' = map length
¿Por qué darle un nombre a una variable que no vas a usar?
Las funciones al curry también ayudan en situaciones como esta:
doubleAndSum ys = map (/xs -> sum (map (*2) xs) ys
doubleAndSum'' = map (sum . map (*2))
Eliminar esas variables adicionales hace que el código sea más fácil de leer y no es necesario que tengas mentalmente claro qué es xs y qué es ys.
HTH.
Puedes ver el curry como una especialización. Elija algunos valores predeterminados y deje al usuario (tal vez usted mismo) con una función especializada, más expresiva .
Un uso efectivo de las funciones al curry es la disminución de la cantidad de código.
Considere tres funciones, dos de las cuales son casi idénticas:
(define (add a b)
(action + a b))
(define (mul a b)
(action * a b))
(define (action kind a b)
(kind a b))
Si su código invoca a add
, a su vez llama a action
con kind +
. Lo mismo con mul
.
Definió estas funciones como lo haría en muchos de los lenguajes populares imperativos disponibles (algunos de ellos incluyen las lambdas, el curry y otras características que generalmente se encuentran en el mundo funcional, porque todo es terriblemente útil).
Todas las sum
y sum
hacen, es envolver la llamada a la action
con el kind
apropiado Ahora, considere las definiciones al curry de estas funciones:
(define add-curried
((curry action) +))
(define mul-curried
((curry action) *))
Se han vuelto considerablemente más cortos. Simplemente aplicamos la action
la función pasándole solo un argumento, el kind
, y obtuvimos la función de curry que acepta el resto de los dos argumentos.
Este enfoque le permite escribir menos código, con un alto nivel de mantenimiento.
Solo imagine que la action
función se reescribiría inmediatamente para aceptar 3 argumentos más. Sin el curry, tendrías que reescribir tus implementaciones de add
y mul
:
(define (action kind a b c d e)
(kind a b c d e))
(define (add a b c d e)
(action + a b c d e))
(define (mul a b c d e)
(action * a b c d e))
Pero el curry te salvó de ese trabajo desagradable y propenso a errores; no tiene que volver a escribir ni siquiera un símbolo en las funciones add-curried
y mul-curried
en absoluto, porque la función de llamada proporcionaría la cantidad necesaria de argumentos pasados a la action
.
Usando all :: (a -> Bool) -> [a] -> Bool
con un predicado al curry.
all (`elem` [1,2,3]) [0,3,4,5]
Los operadores de infijo de Haskell se pueden aplicar a cualquier lado, de modo que puede desplazar fácilmente la aguja o el lado del recipiente de la función elem
(is-element-of).