82 votes

Deux méthodes de curage en Scala ; quel est le cas d'utilisation de chacune ?

J'ai une discussion sur Listes de paramètres multiples dans le guide de style Scala que je maintiens. Je me suis rendu compte qu'il y a deux manières de currying 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 laisse entendre à tort qu'il s'agit de la même chose, alors que ce n'est clairement pas le cas. Le guide essaie de faire une remarque sur les fonctions curry créées, et, bien que la deuxième forme ne soit pas du curry "by-the-book", elle est très similaire à la première forme (bien qu'elle soit plus facile à utiliser car vous n'avez pas besoin de la balise _ )

Parmi ceux qui utilisent ces formulaires, quel est le consensus pour savoir quand utiliser un formulaire plutôt qu'un autre ?

131voto

retronym Points 35066

Méthodes de liste à paramètres multiples

Pour l'inférence de type

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

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

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

Si c'était écrit comme ça :

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 des méthodes de section à paramètres multiples consiste à créer une API qui ressemble à une construction linguistique. L'appelant peut utiliser des accolades à la place des parenthèses.

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

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

L'application de N listes d'arguments à une méthode avec M sections de paramètres, où N < M, peut être convertie en une fonction explicitement avec un _ ou implicitement, avec un type attendu de FunctionN[..] . Il s'agit d'une fonctionnalité de sécurité, voir les notes de modification de Scala 2.0, dans les références Scala, pour en savoir plus.

Fonctions au curry

Les fonctions curées (ou simplement, les fonctions qui renvoient des fonctions) peuvent plus facilement être appliquées à des listes de N arguments.

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

Cette petite commodité en vaut parfois la peine. Notez que les fonctions ne peuvent pas être paramétrées par type, donc dans certains cas une méthode est nécessaire.

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

Calculs en plusieurs étapes

Où d'autre les fonctions curry sont-elles utiles ? Voici un modèle qui revient souvent :

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 pouvons-nous partager le résultat f(t) ? Une solution courante consiste à fournir une version vectorisée de la fonction v :

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

C'est moche ! Nous avons enchevêtré des préoccupations sans rapport les unes avec les autres - le calcul g(f(t), k) et le mappage 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 pouvons é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 comportant plusieurs sections de paramètres, nous sommes bloqués :

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

16voto

Landei Points 30509

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

Compte tenu de la différence entre les méthodes et les fonctions (par exemple, du point de vue de la JVM), Scala fait un bon travail en brouillant la ligne entre elles et en faisant "la bonne chose" dans la plupart des cas, mais il y a est une différence, et parfois vous avez juste besoin de le savoir.

5voto

ddekany Points 5768

Je pense que cela aide à saisir les différences si j'ajoute qu'avec def add(a: Int)(b: Int): Int il suffit de définir une méthode avec deux seuls ces deux paramètres sont regroupés dans 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érale, 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 ce sera scala.Function add2(int a) . Lorsque vous écrivez add2(1) En Scala, il s'agit simplement d'un appel de méthode (par opposition à une fonction littérale).

Notez également que add a (potentiellement) moins de frais généraux que add2 a si vous fournissez immédiatement tous les paramètres. Comme add(5)(6) se traduit simplement par add(5, 6) au niveau de la JVM, aucune Function est créé. D'autre part, add2(5)(6) va d'abord créer un Function qui englobe 5 et ensuite appeler apply(6) sur ce point.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X