2011-01-15 11 views
6

En Scala, puedo definir una función con dos listas de parámetros.scala currying por funciones anidadas o por listas de parámetros múltiples

def myAdd(x :Int)(y :Int) = x + y 

Esto hace que sea fácil definir una función parcialmente aplicada.

val plusFive = myAdd(5) _ 

Pero puedo lograr algo similar definiendo y devolviendo una función anidada.

def myOtherAdd(x :Int) = { 
    def f(y :Int) = x + y 
    f _ 
    } 

Estéticamente, he movido el guión bajo, pero esto todavía se siente como currying.

val otherPlusFive = myOtherAdd(5) 

¿Qué criterios debo usar para preferir un enfoque sobre el otro?

Respuesta

6

También puede hacer esto:

def yetAnotherAdd(x: Int) = x + (_: Int) 

Debe elegir la API se basa en la intención. La razón principal en Scala para tener listas de parámetros múltiples es ayudar a escribir la inferencia. Por ejemplo:

def f[A](x: A)(f: A => A) = ... 
f(5)(_ + 5) 

También se puede usar para tener varios varargs, pero nunca he visto un código como ese. Y, por supuesto, existe la necesidad de la lista de parámetros implícitos, pero eso es más o menos otra cuestión.

Ahora, hay muchas maneras en que puede tener funciones para devolver funciones, que es más o menos lo que hace currying. Debería utilizarlos si se considera que la API es una función que devuelve una función.

Creo que es difícil ser más preciso que esto.

+0

Gracias, Daniel. Su comentario sobre la inferencia de tipo como la motivación para las listas de parámetros múltiples es revelador. Esto fue realmente útil. –

+0

La otra motivación es escribir DSL que se integren bien en el lenguaje, algo así como 'using (x) {...}'. Aunque esto, ciertamente, también hace uso del tipo de inferencia otorgada a múltiples bloques. –

9

Hay por lo menos cuatro maneras de lograr lo mismo:

def myAddA(x: Int, y: Int) = x + y 
val plusFiveA: Int => Int = myAddA(5,_) 

def myAddB(x: Int)(y : Int) = x + y 
val plusFiveB = myAddB(5) _ 

def myAddC(x: Int) = (y: Int) => x + y 
val plusFiveC = myAddC(5) 

def myAddD(x: Int) = { 
    def innerD(y: Int) = x + y 
    innerD _ 
} 
val plusFiveD = myAddD(5) 

Es posible que desee saber que es más eficiente o que es el mejor estilo (por alguna medida basada en el incumplimiento de los mejores).

En lo que respecta a la eficiencia, resulta que los cuatro son esencialmente equivalentes. Los dos primeros casos realmente emiten exactamente el mismo bytecode; la JVM no sabe nada acerca de las listas de parámetros múltiples, por lo que una vez que el compilador se da cuenta (debe ayudarlo con una anotación de tipo en el caso A), todo es lo mismo bajo el capó. El tercer caso también es extremadamente cercano, pero como promete desde el principio devolver una función y la especifica en el lugar, puede evitar un campo interno. El cuarto caso es prácticamente el mismo que los primeros dos en términos de trabajo realizado; simplemente hace la conversión a Function1 dentro del método en lugar de afuera.

En términos de estilo, sugiero que B y C son las mejores maneras de hacerlo, dependiendo de lo que esté haciendo. Si su caso de uso primario es crear una función, no llamar in situ con ambas listas de parámetros, entonces use C porque le dice lo que va a hacer. (Esta versión también es particularmente familiar para las personas que vienen de Haskell, por ejemplo.) Por otro lado, si lo va a llamar en su lugar pero solo ocasionalmente lo curry, entonces use B. De nuevo, dice más claramente qué se espera que haga.

+0

Gracias, Rex. No conocía los enfoques A y C. Su respuesta es realmente útil. –

4

Otra ventaja de hacer que un método devuelva una función directamente (en lugar de usar una aplicación parcial) es que conduce a un código mucho más limpio cuando se usa notación infija, lo que le permite evitar una carga de paréntesis y guiones bajos en expresiones más complejas.

considerar:

val list = List(1,2,3,4) 

def add1(a: Int)(b: Int) = a + b 
list map { add1(5) _ }  

//versus 

def add2(a: Int) = a + (_: Int) 
list map add2(5) 
Cuestiones relacionadas