scala functional-programming currying

Scala currying vs funciones parcialmente aplicadas



functional-programming (4)

Me doy cuenta de que hay varias preguntas aquí sobre qué currying y funciones parcialmente aplicadas, pero estoy preguntando sobre cómo son diferentes. Como un simple ejemplo, aquí hay una función curried para encontrar números pares:

def filter(xs: List[Int], p: Int => Boolean): List[Int] = if (xs.isEmpty) xs else if (p(xs.head)) xs.head :: filter(xs.tail, p) else filter(xs.tail, p) def modN(n: Int)(x: Int) = ((x % n) == 0)

Entonces, podrías escribir lo siguiente para usar esto:

val nums = List(1,2,3,4,5,6,7,8) println(filter(nums, modN(2))

que devuelve: List(2,4,6,8) . Pero descubrí que puedo hacer lo mismo de esta manera:

def modN(n: Int, x: Int) = ((x % n) == 0) val p = modN(2, _: Int) println(filter(nums, p))

que también devuelve: List(2,4,6,8) .

Entonces mi pregunta es, ¿cuál es la principal diferencia entre los dos, y cuándo usarías uno sobre el otro? ¿Es esto demasiado simplista de un ejemplo para mostrar por qué uno se usaría sobre el otro?


Currying tiene que ver con tuplas: convertir una función que toma un argumento tuple en uno que toma n argumentos separados, y viceversa . Recordar esto es la clave para distinguir el curry de la aplicación parcial, incluso en idiomas que no admiten limpiamente el currying.

curry :: ((a, b) -> c) -> a -> b -> c -- curry converts a function that takes all args in a tuple -- into one that takes separate arguments uncurry :: (a -> b -> c) -> (a, b) -> c -- uncurry converts a function of separate args into a function on pairs.

La aplicación parcial es la capacidad de aplicar una función a algunos argumentos, produciendo una nueva función para los argumentos restantes .

Es fácil de recordar si solo piensas que currar es la transformación que tiene que ver con las tuplas.

En los idiomas que están currificados por defecto (como Haskell), la diferencia es clara: hay que hacer algo para pasar argumentos en una tupla. Pero la mayoría de los demás lenguajes, incluido Scala, no están activos por defecto: todos los argumentos se pasan como tuplas, por lo que curry / uncurry es mucho menos útil y menos obvio. Y la gente incluso termina pensando que la aplicación parcial y el currículum son lo mismo, ¡simplemente porque no pueden representar fácilmente las funciones al curry!


Función multivariable:

def modN(n: Int, x: Int) = ((x % n) == 0)

Currying (o la función al curry):

def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

Por lo tanto, no se aplica parcialmente la función que es comparable al currying. Es la función multivariable. Lo que es comparable a la función parcialmente aplicada es el resultado de invocación de una función curried, que es una función con la misma lista de parámetros que tiene la función parcialmente aplicada.


La diferencia semántica se ha explicado bastante bien en la respuesta vinculada por Plasty Grove .

En términos de funcionalidad, no parece mucha diferencia, sin embargo. Veamos algunos ejemplos para verificar eso. Primero, una función normal:

scala> def modN(n: Int, x: Int) = ((x % n) == 0) scala> modN(5, _ : Int) res0: Int => Boolean = <function1>

Entonces obtenemos una <function1> parcialmente aplicada que toma un Int , porque ya le hemos dado el primer entero. Hasta aquí todo bien. Ahora a currying:

scala> def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

Con esta notación, ingenuamente esperarías que lo siguiente funcione:

scala> modNCurried(5) <console>:9: error: missing arguments for method modN; follow this method with `_'' if you want to treat it as a partially applied function modNCurried(5)

Por lo tanto, la notación de lista de parámetros múltiples en realidad no parece crear una función curried de inmediato (suponiendo que se evite una sobrecarga innecesaria) pero espera que usted indique explícitamente que desea que se curried (la notación también tiene otras ventajas ):

scala> modNCurried(5) _ res24: Int => Boolean = <function1>

Que es exactamente lo mismo que obtuvimos antes, por lo que no hay diferencia aquí, a excepción de la notación. Otro ejemplo:

scala> modN _ res35: (Int, Int) => Boolean = <function2> scala> modNCurried _ res36: Int => (Int => Boolean) = <function1>

Esto demuestra cómo la aplicación parcial de una función "normal" da como resultado una función que toma todos los parámetros, mientras que al aplicar parcialmente una función con múltiples listas de parámetros se crea una cadena de funciones, una por lista de parámetros que devuelve una nueva función:

scala> def foo(a:Int, b:Int)(x:Int)(y:Int) = a * b + x - y scala> foo _ res42: (Int, Int) => Int => (Int => Int) = <function2> scala> res42(5) <console>:10: error: not enough arguments for method apply: (v1: Int, v2: Int)Int => (Int => Int) in trait Function2. Unspecified value parameter v2.

Como puede ver, debido a que la primera lista de parámetros de foo tiene dos parámetros, la primera función en la cadena curried tiene dos parámetros.

En resumen, las funciones parcialmente aplicadas no son realmente diferentes de las funciones curried en términos de funcionalidad. Esto se verifica fácilmente dado que puede convertir cualquier función en una al curry:

scala> (modN _).curried res45: Int => (Int => Boolean) = <function1 scala> modNCurried _ res46: Int => (Int => Boolean) = <function1>

Post Scriptum

Nota: La razón por la que su println(filter(nums, modN(2)) ejemplo println(filter(nums, modN(2)) funciona sin el guión bajo después de modN(2) parece ser que el compilador Scala simplemente asume ese guión bajo como una conveniencia para el programador.

Adición: Como @asflierl ha señalado correctamente, Scala no parece ser capaz de inferir el tipo cuando aplica parcialmente funciones "normales":

scala> modN(5, _) <console>:9: error: missing parameter type for expanded function ((x$1) => modN(5, x$1))

Mientras que esa información está disponible para funciones escritas usando la notación de lista de parámetros múltiples:

scala> modNCurried(5) _ res3: Int => Boolean = <function1>

Esta respuesta muestra cómo esto puede ser muy útil.


Solo para aclarar sobre el último punto

Adición: Como @asflierl ha señalado correctamente, Scala no parece ser capaz de inferir el tipo cuando aplica parcialmente funciones "normales":

Scala puede inferir tipos si todos los parámetros son comodines, pero no cuando se especifican algunos y otros no.

scala> modN(_,_) res38: (Int, Int) => Boolean = <function2> scala> modN(1,_) <console>:13: error: missing parameter type for expanded function ((x$1) => modN(1, x$1)) modN(1,_) ^