¿Cuál es la diferencia entre múltiples listas de parámetros y múltiples parámetros por lista en Scala?

81

En Scala se pueden escribir (¿curry?) Funciones como esta

def curriedFunc(arg1: Int) (arg2: String) = { ... }

¿Cuál es la diferencia entre la curriedFuncdefinición de función anterior con dos listas de parámetros y funciones con múltiples parámetros en una sola lista de parámetros?

def curriedFunc(arg1: Int, arg2: String) = { ... }

Desde un punto de vista matemático esto es (curriedFunc(x))(y)y curriedFunc(x,y)pero puedo escribir def sum(x) (y) = x + yy lo mismo serádef sum2(x, y) = x + y

Solo conozco una diferencia: estas son funciones parcialmente aplicadas. Pero ambas formas son equivalentes para mí.

¿Hay otras diferencias?

den bardadym
fuente

Respuestas:

88

Estrictamente hablando, esta no es una función curry, sino un método con múltiples listas de argumentos, aunque ciertamente parece una función.

Como dijiste, las listas de argumentos múltiples permiten que el método se use en lugar de una función aplicada parcialmente. (Perdón por los ejemplos generalmente tontos que uso)

object NonCurr {
  def tabulate[A](n: Int, fun: Int => A) = IndexedSeq.tabulate(n)(fun)
}

NonCurr.tabulate[Double](10, _)            // not possible
val x = IndexedSeq.tabulate[Double](10) _  // possible. x is Function1 now
x(math.exp(_))                             // complete the application

Otro beneficio es que puede usar llaves en lugar de paréntesis, lo que se ve bien si la segunda lista de argumentos consiste en una sola función o procesador. P.ej

NonCurr.tabulate(10, { i => val j = util.Random.nextInt(i + 1); i - i % 2 })

versus

IndexedSeq.tabulate(10) { i =>
  val j = util.Random.nextInt(i + 1)
  i - i % 2
}

O para el thunk:

IndexedSeq.fill(10) {
  println("debug: operating the random number generator")
  util.Random.nextInt(99)
}

Otra ventaja es que puede consultar los argumentos de una lista de argumentos anterior para definir valores de argumento predeterminados (aunque también podría decir que es una desventaja que no puede hacer eso en una lista única :)

// again I'm not very creative with the example, so forgive me
def doSomething(f: java.io.File)(modDate: Long = f.lastModified) = ???

Finalmente, hay otras tres aplicaciones en una respuesta a una publicación relacionada. ¿Por qué Scala proporciona listas de parámetros múltiples y parámetros múltiples por lista? . Los copiaré aquí, pero el mérito es de Knut Arne Vedaa, Kevin Wright y extempore.

Primero: puede tener varios argumentos var:

def foo(as: Int*)(bs: Int*)(cs: Int*) = as.sum * bs.sum * cs.sum

... que no sería posible en una sola lista de argumentos.

En segundo lugar, ayuda a la inferencia de tipos:

def foo[T](a: T, b: T)(op: (T,T) => T) = op(a, b)
foo(1, 2){_ + _}   // compiler can infer the type of the op function

def foo2[T](a: T, b: T, op: (T,T) => T) = op(a, b)
foo2(1, 2, _ + _)  // compiler too stupid, unfortunately

Y por último, esta es la única forma en que puede tener implicitargumentos implícitos y no implícitos, al igual que un modificador para una lista completa de argumentos:

def gaga [A](x: A)(implicit mf: Manifest[A]) = ???   // ok
def gaga2[A](x: A, implicit mf: Manifest[A]) = ???   // not possible
0__
fuente
2
Dado que es la respuesta más votada, creo que el título de la pregunta ya no corresponde a su respuesta. Creo que el título debería cambiarse a, por ejemplo, "¿Por qué Scala proporciona tanto listas de parámetros múltiples como parámetros por lista?", Es decir, ya está combinado con las muestras con stackoverflow.com/questions/4684185/… .
Jacek Laskowski
42

Hay otra diferencia que no fue cubierta por la excelente respuesta de 0 __ : los parámetros predeterminados. Se puede utilizar un parámetro de una lista de parámetros al calcular el valor predeterminado en otra lista de parámetros, pero no en la misma.

Por ejemplo:

def f(x: Int, y: Int = x * 2) = x + y // not valid
def g(x: Int)(y: Int = x * 2) = x + y // valid
Daniel C. Sobral
fuente
Tomé este simple ejemplo para asimilarlo. Eso hace que los parámetros predeterminados sean mucho más útiles. ¡Gracias!
Mike McFarland
Buen ejemplo, excepto que dediqué cinco minutos a averiguar cómo llamarlo: g(1)()devuelve 3. g(1)(2)devuelve 5.
Sapience
19

¡Ese es el punto, es que las formas con curry y sin curry son equivalentes! Como han señalado otros, una u otra forma puede ser sintácticamente más conveniente para trabajar dependiendo de la situación, y esa es la única razón para preferir una sobre la otra.

Es importante entender que incluso si Scala no tuviera una sintaxis especial para declarar funciones curry, aún podría construirlas; esto es solo una inevitabilidad matemática una vez que tenga la capacidad de crear funciones que devuelvan funciones.

Para demostrar esto, imagine que la def foo(a)(b)(c) = {...}sintaxis no existe. Entonces todavía se podía lograr exactamente lo mismo de esta manera: def foo(a) = (b) => (c) => {...}.

Como muchas funciones en Scala, esto es solo una conveniencia sintáctica para hacer algo que sería posible de todos modos, pero con un poco más de verbosidad.

Tom Crockett
fuente
4

Las dos formas son isomorfas. La principal diferencia es que las funciones con curry son más fáciles de aplicar parcialmente, mientras que las funciones sin curry tienen una sintaxis un poco más agradable, al menos en Scala.

Hammar
fuente
2
¿No se ha dicho anteriormente que los ejemplos no son funciones al curry? Entiendo que una función curry tiene un solo argumento y puede devolver una función con un solo argumento y así sucesivamente hasta que haya un cuerpo con todos los argumentos cerrados. ¿Me equivoco?
Jacek Laskowski