La diferencia semántica se ha explicado bastante bien en la respuesta vinculada por Plasty Grove .
Sin embargo, en términos de funcionalidad, no parece haber mucha diferencia. Veamos algunos ejemplos para verificarlo. Primero, una función normal:
scala> def modN(n: Int, x: Int): Boolean = ((x % n) == 0)
scala> modN(5, _ : Int)
res0: Int => Boolean = <function1>
Entonces obtenemos un parcialmente aplicado <function1>
que toma an Int
, porque ya le hemos dado el primer número entero. Hasta ahora tan bueno. Ahora al curry:
scala> def modNCurried(n: Int)(x: Int): Boolean = ((x % n) == 0)
Con esta notación, ingenuamente esperaría que funcione lo siguiente:
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 realmente no parece crear una función curry de inmediato (supuestamente para evitar gastos generales innecesarios), sino que espera a que indique explícitamente que desea curry (la notación también tiene otras ventajas ):
scala> modNCurried(5) _
res24: Int => Boolean = <function1>
Que es exactamente lo mismo que obtuvimos antes, así que no hay diferencia aquí, excepto por 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 la aplicación parcial de una función con múltiples listas de parámetros crea una cadena de funciones, una por lista de parámetros que, todas devuelven una nueva función:
scala> def foo(a:Int, b:Int)(x:Int)(y:Int): 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 de curry tiene dos parámetros.
En resumen, las funciones parcialmente aplicadas no son realmente diferentes de las funciones curry en términos de funcionalidad. Esto se verifica fácilmente dado que puede convertir cualquier función a una 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 ejemplo println(filter(nums, modN(2))
funciona sin el subrayado después modN(2)
parece ser que el compilador de Scala simplemente asume ese subrayado como una conveniencia para el programador.
Además: 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))
Considerando que esa información está disponible para funciones escritas usando 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.