parte - Dos formas de currying en Scala; ¿Cuál es el caso de uso para cada uno?
recorrer lista en scala (3)
Múltiples métodos de lista de parámetros
Para tipo de inferencia
Los métodos con múltiples secciones de parámetros se pueden usar para ayudar a la inferencia de tipo local, mediante el uso de 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 fuera esto escrito como:
def foldLeft[B](z: B, op: (B, A) => B): B
Uno debería proporcionar tipos más explícitos:
List("").foldLeft(0, (b: Int, a: String) => a + b.length)
List("").foldLeft[Int](0, _ + _.length)
Para una API fluida
Otro uso de los métodos de sección de parámetros múltiples es crear una API que se parece a una construcción de lenguaje. El que llama puede usar llaves en vez 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 argumentos enumera al método con M secciones de parámetros, donde N <M, se puede convertir a una función explícitamente con un _
, o implícitamente, con un tipo esperado de la 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 curried (o simplemente 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 conveniencia menor a veces vale la pena. 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 multi etapa
¿En qué otro lugar son útiles las funciones al curry? Aquí hay un patrón que aparece todo el tiempo:
def v(t: Double, k: Double): Double = {
// expensive computation based only on t
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 sobre 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 tratamos de 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!
}
Estoy manteniendo una discusión sobre las Listas de Parámetros Múltiples en la Guía de Estilo de Scala que mantengo. Me he dado cuenta de que hay dos formas de currying , y me pregunto cuáles son los casos de uso:
def add(a:Int)(b:Int) = {a + b}
// Works
add(5)(6)
// Doesn''t compile
val f = add(5)
// Works
val f = add(5)_
f(10) // yields 15
def add2(a:Int) = { b:Int => a + b }
// Works
add2(5)(6)
// Also works
val f = add2(5)
f(10) // Yields 15
// Doesn''t compile
val f = add2(5)_
La guía de estilo implica incorrectamente que estos son iguales, cuando claramente no lo son. La guía intenta hacer una observación sobre las funciones currificadas creadas y, aunque la segunda forma no es currículum "por libro", sigue siendo muy similar a la primera (aunque podría decirse que es más fácil de usar porque no necesita el _
)
De aquellos que usan estos formularios, ¿cuál es el consenso sobre cuándo usar una forma sobre la otra?
Creo que ayuda comprender las diferencias si agrego eso con def add(a: Int)(b: Int): Int
. Simplemente defines un método con dos parámetros, solo esos dos parámetros están agrupados en dos listas de parámetros (ver las consecuencias de eso en otros comentarios). De hecho, ese método es simplemente int add(int a, int a)
en lo que concierne a Java (¡no a Scala!). Cuando escribe add(5)_
, eso es solo una función literal, una forma más corta de { b: Int => add(1)(b) }
. Por otro lado, con add2(a: Int) = { b: Int => a + b }
define un método que tiene solo un parámetro, y para Java será scala.Function add2(int a)
. Cuando escribe add2(1)
en Scala es solo una llamada de método simple (en contraposición a un literal de función).
También tenga en cuenta que add
tiene (potencialmente) menos gastos generales que add2
si proporciona inmediatamente todos los parámetros. Al igual que add(5)(6)
simplemente se traduce para add(5, 6)
en el nivel JVM, no se crea ningún objeto Function
. Por otro lado, add2(5)(6)
primero creará un objeto Function
que encierre 5
, y luego invoque apply(6)
sobre eso.
Puedes curry solo con funciones, no con 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 son los diferentes métodos y funciones (por ejemplo, desde la perspectiva de la JVM), Scala hace un buen trabajo borrando la línea entre ellos y haciendo "Lo correcto" en la mayoría de los casos, pero hay una diferencia, y a veces solo necesitas para saber sobre eso