Métodos de lista de parámetros múltiples
Para la inferencia de tipo
Los métodos con múltiples secciones de parámetros se pueden usar para ayudar a la inferencia de tipos locales, usando parámetros en la primera sección para inferir argumentos de tipo que proporcionarán un tipo esperado para un argumento en la sección siguiente. foldLeft
en la biblioteca estándar es el ejemplo canónico de esto.
def foldLeft[B](z: B)(op: (B, A) => B): B
List("").foldLeft(0)(_ + _.length)
Si esto estuviera escrito como:
def foldLeft[B](z: B, op: (B, A) => B): B
Habría que proporcionar tipos más explícitos:
List("").foldLeft(0, (b: Int, a: String) => a + b.length)
List("").foldLeft[Int](0, _ + _.length)
Para API fluida
Otro uso de los métodos de sección de parámetros múltiples es crear una API que parezca una construcción de lenguaje. La persona que llama puede usar llaves en lugar de paréntesis.
def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)
loop(2) {
println("hello!")
}
La aplicación de N listas de argumentos a métodos con M secciones de parámetros, donde N <M, se puede convertir a una función explícitamente con a _
, o implícitamente, con un tipo esperado de FunctionN[..]
. Esta es una característica de seguridad, vea las notas de cambio para Scala 2.0, en las Referencias de Scala, para un fondo.
Funciones al curry
Las funciones curry (o simplemente, las funciones que devuelven funciones) se pueden aplicar más fácilmente a N listas de argumentos.
val f = (a: Int) => (b: Int) => (c: Int) => a + b + c
val g = f(1)(2)
Esta pequeña conveniencia a veces vale la pena. Sin embargo, tenga en cuenta que las funciones no pueden ser de tipo paramétrico, por lo que en algunos casos se requiere un método.
Su segundo ejemplo es un híbrido: un método de sección de un parámetro que devuelve una función.
Computación de múltiples etapas
¿Dónde más son útiles las funciones de curry? Aquí hay un patrón que aparece todo el tiempo:
def v(t: Double, k: Double): Double = {
val ft = f(t)
g(ft, k)
}
v(1, 1); v(1, 2);
¿Cómo podemos compartir el resultado f(t)
? Una solución común es proporcionar una versión vectorizada de v
:
def v(t: Double, ks: Seq[Double]: Seq[Double] = {
val ft = f(t)
ks map {k => g(ft, k)}
}
¡Feo! Hemos enredado preocupaciones no relacionadas: calcular g(f(t), k)
y mapear una secuencia de ks
.
val v = { (t: Double) =>
val ft = f(t)
(k: Double) => g(ft, k)
}
val t = 1
val ks = Seq(1, 2)
val vs = ks map (v(t))
También podríamos usar un método que devuelva una función. En este caso, es un poco más legible:
def v(t:Double): Double => Double = {
val ft = f(t)
(k: Double) => g(ft, k)
}
Pero si intentamos hacer lo mismo con un método con múltiples secciones de parámetros, nos quedamos atascados:
def v(t: Double)(k: Double): Double = {
^
`-- Can't insert computation here!
}
def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)
val f: (a: Int) => (b: Int) => (c: Int) = a + b + c
Puede curry solo funciones, no métodos.
add
es un método, por lo que necesita_
para forzar su conversión a una función.add2
devuelve una función, por lo_
que no solo es innecesario, sino que no tiene sentido aquí.Teniendo en cuenta cómo los diferentes métodos y funciones son (por ejemplo, desde la perspectiva de la JVM), Scala hace un trabajo bastante bueno borrando la línea entre ellos y haciendo "lo correcto" en la mayoría de los casos, pero no es una diferencia, y a veces sólo tiene para saberlo.
fuente
Creo que ayuda a comprender las diferencias si agrego que con
def add(a: Int)(b: Int): Int
usted prácticamente solo defina un método con dos parámetros, solo esos dos parámetros se agrupan en dos listas de parámetros (vea las consecuencias de eso en otros comentarios). De hecho, ese método es en loint add(int a, int a)
que respecta a Java (¡no a Scala!). Cuando escribeadd(5)_
, es solo una función literal, una forma más corta de{ b: Int => add(1)(b) }
. Por otro lado, conadd2(a: Int) = { b: Int => a + b }
usted define un método que tiene solo un parámetro, y para Java lo seráscala.Function add2(int a)
. Cuando escribeadd2(1)
en Scala, es solo una llamada a un método simple (a diferencia de una función literal).También tenga en cuenta que
add
tiene (potencialmente) menos gastos generales queadd2
si proporciona inmediatamente todos los parámetros. Comoadd(5)(6)
simplemente se traduce enadd(5, 6)
en el nivel de JVM, noFunction
se crea ningún objeto. Por otro lado,add2(5)(6)
primero creará unFunction
objeto que encierra5
, y luego lo invocaráapply(6)
.fuente