¿Qué tiene de especial el curry o la aplicación parcial?

9

He estado leyendo artículos sobre programación funcional todos los días y he tratado de aplicar algunas prácticas tanto como sea posible. Pero no entiendo qué es único en el curry o la aplicación parcial.

Tome este código Groovy como ejemplo:

def mul = { a, b -> a * b }
def tripler1 = mul.curry(3)
def tripler2 = { mul(3, it) }

No entiendo cuál es la diferencia entre tripler1y tripler2. ¿No son los dos iguales? El 'curry' es compatible con lenguajes funcionales puros o parciales como Groovy, Scala, Haskell, etc. Pero puedo hacer lo mismo (left-curry, right-curry, n-curry o aplicación parcial) simplemente creando otro nombre o anónimo función o cierre que reenviará los parámetros a la función original (como tripler2) en la mayoría de los idiomas (incluso C.)

¿Me estoy perdiendo de algo? Hay lugares donde puedo usar el currículum y la aplicación parcial en mi solicitud de Grails, pero dudo en hacerlo porque me pregunto "¿Cómo es eso diferente?"

Por favor iluminame.

EDITAR: ¿Están diciendo que la aplicación / currículum parcial es simplemente más eficiente que crear / llamar a otra función que reenvía los parámetros predeterminados a la función original?

Vigneshwaran
fuente
1
¿Alguien puede crear las etiquetas "curry" o "curry"?
Vigneshwaran
¿Cómo curry en C?
Giorgio
Probablemente esto sea más acerca de los programadores de
jk.
1
@Vigneshwaran: AFAIK no necesita crear otra función en un idioma que soporte el curry. Por ejemplo, en Haskell f x y = x + ysignifica que fes una función que toma un parámetro int. El resultado de f x( faplicado a x) es una función que toma un parámetro int. El resultado f x y(o (f x) y, es decir, f xaplicado a y) es una expresión que no toma parámetros de entrada y se evalúa reduciendo x + y.
Giorgio
1
Puede lograr las mismas cosas, pero la cantidad de esfuerzo que realiza con C es mucho más dolorosa y no tan eficiente como en un lenguaje como Haskell, donde es el comportamiento predeterminado
Daniel Gratzer

Respuestas:

8

El curry se trata de convertir / representar una función que toma n entradas en n funciones que cada una toma 1 entrada. La aplicación parcial se trata de arreglar algunas de las entradas a una función.

La motivación para la aplicación parcial es principalmente que facilita la escritura de bibliotecas de funciones de orden superior. Por ejemplo, los algoritmos en C ++ STL toman en gran medida predicados o funciones unarias, bind1st permite al usuario de la biblioteca enganchar funciones no unarias con un valor enlazado. Por lo tanto, el escritor de la biblioteca no necesita proporcionar funciones sobrecargadas para todos los algoritmos que toman funciones unarias para proporcionar versiones binarias

El curring en sí mismo es útil porque le brinda una aplicación parcial en cualquier lugar que desee de forma gratuita, es decir, ya no necesita una función como bind1staplicar parcialmente.

jk.
fuente
¿Hay curryingalgo específico para groovy o aplicable en todos los idiomas?
anfibio
@foampile es algo que es aplicable en todos los idiomas, pero irónicamente, el curry maravilloso realmente no lo hace programmers.stackexchange.com/questions/152868/…
jk.
@jk. ¿Estás diciendo que la aplicación curry / parcial es más eficiente que crear y llamar a otra función?
Vigneshwaran
2
@Vigneshwaran: no es necesariamente más eficiente, pero definitivamente es más eficiente en términos del tiempo del programador. También tenga en cuenta que aunque el currículum es compatible con muchos lenguajes funcionales, pero generalmente no es compatible con OO o lenguajes de procedimiento. (O al menos, no por el lenguaje en sí.)
Stephen C
6

Pero puedo hacer lo mismo (curry izquierdo, curry derecho, n-curry o aplicación parcial) simplemente creando otra función o cierre con nombre o anónimo que reenvíe los parámetros a la función original (como tripler2) en la mayoría de los idiomas ( incluso C.)

Y el optimizador lo verá y pasará rápidamente a algo que pueda entender. El curry es un pequeño y agradable truco para el usuario final, pero tiene beneficios mucho mejores desde el punto de vista del diseño del lenguaje. Es realmente agradable manejar todos los métodos como unarios, A -> Bdonde Bpuede haber otro método.

Simplifica qué métodos tiene que escribir para manejar funciones de orden superior. Su análisis estático y optimización en el idioma solo tiene una ruta para trabajar que se comporta de manera conocida. La unión de parámetros simplemente se cae del diseño en lugar de requerir que los aros hagan este comportamiento común.

Telastyn
fuente
6

Como @jk. Aludido, curry puede ayudar a hacer el código más general.

Por ejemplo, suponga que tiene estas tres funciones (en Haskell):

> let q a b = (2 + a) * b

> let r g = g 3

> let f a b = b (a 1)

La función faquí toma dos funciones como argumentos, pasa 1a la primera función y pasa el resultado de la primera llamada a la segunda función.

Si tuviéramos que llamar fusando qy rcomo argumentos, efectivamente estaría haciendo:

> r (q 1)

donde qse aplicaría 1y devolvería otra función (como qse curry); esta función devuelta se pasaría a rsu argumento para que se le dé un argumento de 3. El resultado de esto sería un valor de 9.

Ahora, digamos que teníamos otras dos funciones:

> let s a = 3 * a

> let t a = 4 + a

podríamos pasar esto ftambién y obtener un valor de 7o 15, dependiendo de si nuestros argumentos eran s to t s. Dado que estas funciones devuelven un valor en lugar de una función, no se llevaría a cabo una aplicación parcial en f s to f t s.

Si hubiéramos escrito fcon qy ren mente, podríamos haber usado una lambda (función anónima) en lugar de una aplicación parcial, por ejemplo:

> let f' a b = b (\x -> a 1 x)

pero esto habría restringido la generalidad de f'. fse puede llamar con argumentos qy ro sy t, pero f'solo se puede llamar con qy r, f' s ty f' t sambos producen un error.

MÁS

Si f'se llamara con un q'/ r'par donde q'tomaron más de dos argumentos, el q'todavía terminaría siendo aplicado parcialmente f'.

Alternativamente, podría envolver qafuera en flugar de adentro, pero eso lo dejaría con una desagradable lambda anidada:

f (\x -> (\y -> q x y)) r

que es esencialmente lo que el curry qera en primer lugar!

Pablo
fuente
Me abriste los ojos. Su respuesta me hizo darme cuenta de cómo las funciones aplicadas al currículum / parcialmente son diferentes de crear una nueva función que pase argumentos a la función original. 1. Pasar por las funciones curryied / paed (como f (q.curry (2)) es más ordenado que crear funciones separadas innecesariamente solo para un uso temporal. (En lenguajes funcionales como groovy)
Vigneshwaran
2. En mi pregunta, dije: "Yo puedo hacer lo mismo en C." Sí, pero en lenguajes no funcionales, donde no puede pasar funciones como datos, crear una función separada, que reenvíe los parámetros al original, no tiene todos los beneficios de curry / pa
Vigneshwaran
Noté que Groovy no admite el tipo de generalización que admite Haskell. Tuve que escribir def f = { a, b -> b a.curry(1) }para hacerme f q, rtrabajar def f = { a, b -> b a(1) }o def f = { a, b -> b a.curry(1)() }para f s, ttrabajar. Tienes que pasar todos los parámetros o decir explícitamente que estás curry. :(
Vigneshwaran
2
@Vigneshwaran: Sí, es seguro decir que Haskell y el curry van muy bien . ;] Tenga en cuenta que en Haskell, las funciones son curry (en la definición correcta) por defecto y el espacio en blanco indica la aplicación de la función, por f x ylo que significa lo que escribirían muchos idiomas f(x)(y), no f(x, y). ¿Quizás su código funcionaría en Groovy si escribe de qmodo que espere que se llame así q(1)(2)?
CA McCann
1
@Vigneshwaran Me alegro de poder ayudar! Siento tu dolor por tener que decir explícitamente que estás haciendo una aplicación parcial. En Clojure tengo que hacerlo (partial f a b ...): al estar acostumbrado a Haskell, extraño mucho el currículum adecuado cuando programo en otros lenguajes (aunque he estado trabajando recientemente en F # que, afortunadamente, lo admite).
Paul
3

Hay dos puntos clave sobre la aplicación parcial. El primero es sintáctico / conveniente: algunas definiciones se vuelven más fáciles y cortas de leer y escribir, como mencionó @jk. (¡Mira la programación de Pointfree para obtener más información sobre lo increíble que es esto!)

El segundo, como mencionó @telastyn, trata sobre un modelo de funciones y no es simplemente conveniente. En la versión de Haskell, de la que obtendré mis ejemplos porque no estoy familiarizado con otros lenguajes con aplicación parcial, todas las funciones toman un solo argumento. Sí, incluso funciona como:

(:) :: a -> [a] -> [a]

tomar un solo argumento; Debido a la asociatividad del constructor del tipo de función ->, lo anterior es equivalente a:

(:) :: a -> ([a] -> [a])

que es una función que toma ay devuelve una función [a] -> [a].

Esto nos permite escribir funciones como:

($) :: (a -> b) -> a -> b

que puede aplicar cualquier función a un argumento del tipo apropiado. Incluso los locos como:

f :: (t, t1) -> t -> t1 -> (t2 -> t3 -> (t, t1)) -> t2 -> t3 -> [(t, t1)]
f q r s t u v = q : (r, s) : [t u v]

f' :: () -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)]
f' = f $ ((), 'a')  -- <== works fine

Bien, ese fue un ejemplo artificial. Pero una más útil involucra la clase de tipo Aplicativo , que incluye este método:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

Como puede ver, el tipo es idéntico al de $quitar el Applicative fbit y, de hecho, esta clase describe la aplicación de funciones en un contexto. Entonces, en lugar de la aplicación de función normal:

ghci> map (+3) [1..5]  
[4,5,6,7,8]

Podemos aplicar funciones en un contexto Aplicativo; por ejemplo, en el contexto Quizás en el que algo puede estar presente o faltante:

ghci> Just map <*> Just (+3) <*> Just [1..5]
Just [4,5,6,7,8]

ghci> Just map <*> Nothing <*> Just [1..5]
Nothing

Ahora la parte realmente genial es que la clase de tipo Aplicativo no menciona nada sobre funciones de más de un argumento; sin embargo, puede tratar con ellas, incluso funciones de 6 argumentos como f:

fA' :: Maybe (() -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)])
fA' = Just f <*> Just ((), 'a')

Hasta donde sé, la clase de tipo Aplicativo en su forma general no sería posible sin alguna concepción de aplicación parcial. (Para cualquier experto en programación, ¡corríjame si me equivoco!) Por supuesto, si su idioma no tiene una aplicación parcial, podría construirlo de alguna forma, pero ... simplemente no es lo mismo, ¿verdad? ? :)


fuente
1
Applicativesin curry o aplicación parcial utilizaría fzip :: (f a, f b) -> f (a, b). En un lenguaje con funciones de orden superior, esto le permite elevar el currículum y la aplicación parcial al contexto del functor y es equivalente a (<*>). Sin las funciones de orden superior no tendrás, fmappor lo que todo sería inútil.
CA McCann
@CAMcCann gracias por los comentarios! Sabía que estaba sobre mi cabeza con esta respuesta. Entonces, ¿está mal lo que dije?
1
Es correcto en espíritu, ciertamente. Dividir los pelos sobre las definiciones de "forma general", "posible", y tener una "concepción de aplicación parcial" no cambiará el simple hecho de que el encantador f <$> x <*> yestilo idiomático funciona fácilmente porque el curry y la aplicación parcial funcionan fácilmente. En otras palabras, lo que es agradable es más importante que lo que es posible aquí.
CA McCann
Cada vez que veo ejemplos de código de programación funcional, estoy más convencido de que es una broma elaborada y de que no existe.
Kieveli
1
@Kieveli es lamentable que te sientas así. Existen muchos tutoriales excelentes que lo ayudarán a comenzar a trabajar con una buena comprensión de los conceptos básicos.