¿Groovy llama a la aplicación parcial 'curry'?

15

Groovy tiene un concepto que llama 'curry'. Aquí hay un ejemplo de su wiki:

def divide = { a, b -> a / b }

def halver = divide.rcurry(2)

assert halver(8) == 4

Mi comprensión de lo que está sucediendo aquí es que el argumento de la mano derecha divideestá vinculado al valor 2. Esto parece una forma de aplicación parcial.

El término curry se usa generalmente para significar transformar una función que toma una serie de argumentos en una función que solo toma un argumento y devuelve otra función. Por ejemplo, aquí está el tipo de la curryfunción en Haskell:

curry :: ((a, b) -> c) -> (a -> (b -> c))

Para personas que no han usado Haskell a, by cson todos parámetros genéricos. currytoma una función con dos argumentos y devuelve una función que toma ay devuelve una función de ba c. Agregué un par adicional de corchetes al tipo para que esto sea más claro.

¿He entendido mal lo que está sucediendo en el maravilloso ejemplo o es simplemente una aplicación parcial mal nombrada? O, de hecho, hace ambas cosas: es decir, convertir divideen una función curry y luego aplicar parcialmente 2a esta nueva función.

Richard Warburton
fuente

Respuestas:

14

La implementación de Groovy en curryrealidad no curry en ningún momento, incluso detrás de escena. Es esencialmente idéntico a la aplicación parcial.

Los curry, rcurryy ncurrymétodos devuelven un CurriedClosureobjeto que contiene los argumentos encuadernados. También tiene un método getUncurriedArguments(mal nombrado: funciones de curry, no argumentos) que devuelve la composición de los argumentos que se le pasan con los argumentos enlazados.

Cuando se llama a un cierre, finalmente llama al invokeMethodmétodo deMetaClassImpl , que verifica explícitamente si el objeto que llama es una instancia de CurriedClosure. Si es así, utiliza lo mencionado anteriormente getUncurriedArgumentspara componer el conjunto completo de argumentos a aplicar:

if (objectClass == CurriedClosure.class) {
    // ...
    final Object[] curriedArguments = cc.getUncurriedArguments(arguments);
    // [Ed: Yes, you read that right, curried = uncurried. :) ]
    // ...
    return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments);
}

Basado en la confusa y algo inconsistente nomenclatura anterior, sospecho que quien escribió esto tiene una buena comprensión conceptual, pero tal vez se apresuró un poco y, como muchas personas inteligentes, combinó el curry con una aplicación parcial. Esto es comprensible (ver la respuesta de Paul King), aunque un poco desafortunado; Será difícil corregir esto sin romper la compatibilidad con versiones anteriores.

Una solución que he sugerido es sobrecargar el currymétodo de tal manera que cuando no se pasan argumentos, esto realmente hace curry, y desaprobar llamar al método con argumentos a favor de una nueva partialfunción. Esto puede parecer un poco extraño , pero maximizaría la compatibilidad con versiones anteriores, ya que no hay razón para usar una aplicación parcial con cero argumentos, al tiempo que evita la situación más fea (en mi humilde opinión) de tener una nueva función con un nombre diferente para un currículum adecuado mientras la función realmente llamadocurry hace algo diferente y confusamente similar.

No hace falta decir que el resultado de llamar curryes completamente diferente del curry real. Si realmente curry la función, podría escribir:

def add = { x, y -> x + y }
def addCurried = add.curry()   // should work like { x -> { y -> x + y } }
def add1 = addCurried(1)       // should work like { y -> 1 + y }
assert add1(1) == 2 

... y funcionaría, porque addCurrieddebería funcionar así { x -> { y -> x + y } }. En cambio, arroja una excepción de tiempo de ejecución y mueres un poco por dentro.

Jordan Gray
fuente
1
Creo que rcurry y ncurry en funciones con argumentos> 2 demuestran que esto es realmente una aplicación parcial, no curry
jk.
@jk De hecho, es demostrable en funciones con argumentos == 2, como señalo al final. :)
Jordan Gray
3
@matcauthon Hablando estrictamente, el "propósito" del curry es transformar una función con muchos argumentos en una cadena de funciones anidadas con un argumento cada una. Creo que lo que está pidiendo es una razón práctica que le quiere utilizar Currying, que es un poco más difícil de justificar en maravilloso ejemplo que en LISP o Haskell. El punto es que lo que probablemente quieras usar la mayor parte del tiempo es una aplicación parcial, no curry.
Jordan Gray
44
+1 paraand you die a little inside
Thomas Eding
1
Wow, entiendo mucho mejor curry después de leer su respuesta: +1 y gracias! Específico a la pregunta, me gusta que dijiste, "curry combinado con aplicación parcial".
GlenPeterson
3

Creo que está claro que el curry maravilloso es en realidad una aplicación parcial cuando se consideran funciones con más de dos argumentos. considerar

f :: (a,b,c) -> d

su forma de curry sería

fcurried :: a -> b -> c -> d

sin embargo, el curry de groovy devolverá algo equivalente a (suponiendo que se llama con 1 argumento x)

fgroovy :: (b,c) -> d 

que llamará a f con el valor de un fijo a x

es decir, si bien el curry de groovy puede devolver funciones con argumentos N-1, las funciones de curry correctamente solo tienen 1 argumento, por lo tanto, groovy no puede curry con curry

jk.
fuente
2

Groovy tomó prestados los nombres de sus métodos de curry de muchos otros lenguajes FP no puros que también usan nombres similares para aplicaciones parciales, tal vez desafortunados para tal funcionalidad centrada en FP. Hay varias implementaciones de curry "reales" que se proponen para su inclusión en Groovy. Un buen hilo para comenzar a leer sobre ellos está aquí:

http://groovy.markmail.org/thread/c4ycxdzm3ack6xxb

La funcionalidad existente se mantendrá de alguna forma y se tendrá en cuenta la compatibilidad con versiones anteriores al hacer una llamada sobre cómo nombrar los nuevos métodos, etc., por lo que no puedo decir en esta etapa cuál será el nombre final de los métodos nuevos / antiguos ser. Probablemente un compromiso en el nombramiento, pero ya veremos.

Para la mayoría de los programadores de OO, la distinción entre los dos términos (currículum y aplicación parcial) es discutible en gran medida académica; sin embargo, una vez que esté acostumbrado a ellos (y quien mantendrá su código está capacitado para leer este estilo de codificación), entonces la programación de estilo libre de puntos o tácita (que admite currículum "real") permite que ciertos tipos de algoritmos se expresen de manera más compacta y en algunos casos con más elegancia. Obviamente, hay algo de "belleza en los ojos del espectador" aquí, pero tener la capacidad de admitir ambos estilos está en consonancia con la naturaleza de Groovy (OO / FP, estática / dinámica, clases / scripts, etc.).

Paul King
fuente
1

Dada esta definición encontrada en IBM:

El término curry está tomado de Haskell Curry, el matemático que desarrolló el concepto de funciones parciales. Curry se refiere a tomar múltiples argumentos en una función que toma muchos argumentos, lo que resulta en una nueva función que toma los argumentos restantes y devuelve un resultado.

halveres su nueva función (currificada) (o cierre), que ahora solo necesita un parámetro. Vocaciónhalver(10) resultaría en 5.

Por lo tanto, transforma una función con n argumentos en una función con n-1 argumentos. Su ejemplo de haskell dice lo mismo que hace el curry.

matcauthon
fuente
44
La definición de IBM es incorrecta. Lo que definen como currículum es en realidad una aplicación de función parcial, que une (arregla) los argumentos de una función para hacer una función con menor arity. El curry transforma una función que toma múltiples argumentos en una cadena de funciones que cada uno toma un argumento.
Jordan Gray
1
En la definición de wikipedia es lo mismo que de IBM: en matemáticas y ciencias de la computación, el currículum es la técnica de transformar una función que toma múltiples argumentos (o una n-tupla de argumentos) de tal manera que pueda llamarse como un cadena de funciones, cada una con un solo argumento (aplicación parcial). Groovy transforma una función (con dos argumentos) con la rcurryfunción (que toma un argumento) en una función (con ahora solo un argumento). Encadené la función curry con un argumento a mi función base para obtener mi función resultante.
matcauthon
3
No, la definición de Wikipedia es diferente: la aplicación parcial es cuando se llama a la función currículum, no cuando se define, que es lo que hace Groovy
jk.
66
@jk es correcto. Lea la explicación de Wikipedia nuevamente y verá que lo que se devuelve es una cadena de funciones con un argumento cada una, no una función con n - 1argumentos. Vea el ejemplo al final de mi respuesta; También vea más adelante en el artículo para más información sobre la distinción que se está haciendo. en.wikipedia.org/wiki/…
Jordan Gray
44
Es muy significativo, confía en mí. Nuevamente, el código al final de mi respuesta demuestra cómo funcionaría una implementación correcta; No tomaría argumentos, para empezar. La implementación actual realmente debería llamarse, por ejemplo partial.
Jordan Gray