web-dev-qa-db-fra.com

Deux façons de curry à Scala; quel est le cas d'utilisation pour chacun?

J'ai une discussion autour de Listes de paramètres multiples dans le Scala Guide de style que je maintiens Je me suis rendu compte qu'il y a deux façons de curry , et je me demande quels sont les cas d'utilisation:

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)_

Le guide de style implique à tort que ce sont les mêmes, alors qu'ils ne le sont clairement pas. Le guide essaie de faire un point sur les fonctions créées au curry, et, bien que le deuxième formulaire ne soit pas un curry "par le livre", il est toujours très similaire au premier formulaire (bien que sans doute plus facile à utiliser parce que vous n'avez pas besoin les _)

Parmi ceux qui utilisent ces formulaires, quel est le consensus sur le moment d'utiliser un formulaire par rapport à l'autre?

83
davetron5000

Méthodes de liste de paramètres multiples

Pour l'inférence de type

Des méthodes avec plusieurs sections de paramètres peuvent être utilisées pour faciliter l'inférence de type locale, en utilisant des paramètres dans la première section pour déduire des arguments de type qui fourniront un type attendu pour un argument dans la section suivante. foldLeft dans la bibliothèque standard en est l'exemple canonique.

def foldLeft[B](z: B)(op: (B, A) => B): B

List("").foldLeft(0)(_ + _.length)

Si cela était écrit comme suit:

def foldLeft[B](z: B, op: (B, A) => B): B

Il faudrait fournir des types plus explicites:

List("").foldLeft(0, (b: Int, a: String) => a + b.length)
List("").foldLeft[Int](0, _ + _.length)

Pour une API fluide

Une autre utilisation de plusieurs méthodes de section de paramètres consiste à créer une API qui ressemble à une construction de langage. L'appelant peut utiliser des accolades au lieu de parenthèses.

def loop[A](n: Int)(body: => A): Unit = (0 until n) foreach (n => body)

loop(2) {
   println("hello!")
}

Application de N listes d'arguments à une méthode avec M sections de paramètres, où N <M, peut être convertie en fonction explicitement avec un _, Ou implicitement, avec un type attendu de FunctionN[..]. Il s'agit d'une fonction de sécurité, voir les notes de changement pour Scala 2.0, dans les références Scala, pour un arrière-plan).

Fonctions curry

Les fonctions curry (ou simplement les fonctions qui renvoient des fonctions) peuvent être appliquées plus facilement à N listes d'arguments.

val f = (a: Int) => (b: Int) => (c: Int) => a + b + c
val g = f(1)(2)

Cette commodité mineure en vaut parfois la peine. Notez que les fonctions ne peuvent pas être de type paramétrique, donc dans certains cas, une méthode est requise.

Votre deuxième exemple est un hybride: une méthode de section à un paramètre qui renvoie une fonction.

Calcul multi-étapes

Où les fonctions au curry sont-elles utiles? Voici un modèle qui revient tout le temps:

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);

Comment partager le résultat f(t)? Une solution courante consiste à fournir une version vectorisée de v:

def v(t: Double, ks: Seq[Double]: Seq[Double] = {
   val ft = f(t)
   ks map {k => g(ft, k)}
}

Laid! Nous avons emmêlé des préoccupations indépendantes - calculer g(f(t), k) et mapper sur une séquence 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))

Nous pourrions également utiliser une méthode qui renvoie une fonction. Dans ce cas, c'est un peu plus lisible:

def v(t:Double): Double => Double = {
   val ft = f(t)
   (k: Double) => g(ft, k)       
}

Mais si nous essayons de faire la même chose avec une méthode avec plusieurs sections de paramètres, nous sommes bloqués:

def v(t: Double)(k: Double): Double = {
                ^
                `-- Can't insert computation here!
}
132
retronym

Vous ne pouvez utiliser que des fonctions, pas des méthodes. add est une méthode, vous avez donc besoin du _ pour forcer sa conversion en fonction. add2 renvoie une fonction, donc le _ n'est pas seulement inutile mais n'a aucun sens ici.

Compte tenu de la façon dont les différentes méthodes et fonctions sont (par exemple du point de vue de la JVM), Scala fait un très bon travail en brouillant la frontière entre elles et en faisant "La bonne chose" dans la plupart des cas, mais là est une différence, et parfois vous avez juste besoin de le savoir.

16
Landei

Je pense que cela aide à saisir les différences si j'ajoute qu'avec def add(a: Int)(b: Int): Int vous définissez à peu près simplement une méthode avec deux paramètres, seuls ces deux paramètres sont regroupés en deux listes de paramètres (voir les conséquences de cela dans d'autres commentaires). En fait, cette méthode est juste int add(int a, int a) en ce qui concerne Java (pas Scala!). Lorsque vous écrivez add(5)_, c'est juste une fonction littéral, une forme plus courte de { b: Int => add(1)(b) }. D'autre part, avec add2(a: Int) = { b: Int => a + b } vous définissez une méthode qui n'a qu'un seul paramètre, et pour Java it sera scala.Function add2(int a). Lorsque vous écrivez add2(1) dans Scala c'est juste un appel de méthode simple (par opposition à une fonction littérale).

Notez également que add a (potentiellement) moins de surcharge que add2 Si vous fournissez immédiatement tous les paramètres. Comme add(5)(6) se traduit simplement par add(5, 6) au niveau JVM, aucun objet Function n'est créé. D'un autre côté, add2(5)(6) créera d'abord un objet Function qui entoure 5, Puis appellera apply(6) à ce sujet.

5
ddekany