web-dev-qa-db-fra.com

Scala currying vs fonctions partiellement appliquées

Je me rends compte qu’il ya plusieurs questions à propos dece quefonctions de currying et partiellement appliquées, mais je demande comment elles sont différentes. À titre d’exemple simple, voici une fonction curry pour trouver des nombres pairs:

def filter(xs: List[Int], p: Int => Boolean): List[Int] =
   if (xs.isEmpty) xs
   else if (p(xs.head)) xs.head :: filter(xs.tail, p)
   else filter(xs.tail, p)

def modN(n: Int)(x: Int) = ((x % n) == 0)

Donc, vous pouvez écrire ce qui suit pour utiliser ceci:

val nums = List(1,2,3,4,5,6,7,8)
println(filter(nums, modN(2))

qui retourne: List(2,4,6,8). Mais j'ai constaté que je pouvais faire la même chose de cette façon:

def modN(n: Int, x: Int) = ((x % n) == 0)

val p = modN(2, _: Int)
println(filter(nums, p))

qui retourne également: List(2,4,6,8)

Ma question est donc la suivante: quelle est la principale différence entre les deux et quand utiliseriez-vous l’une sur l’autre? Est-ce simplement un exemple trop simpliste pour montrer pourquoi l'un serait utilisé plutôt que l'autre?

81
Eric

La différence sémantique a été assez bien expliquée dans la réponse liée à Plasty Grove .

En termes de fonctionnalité, il ne semble pas y avoir beaucoup de différence, cependant. Regardons quelques exemples pour vérifier cela. Tout d'abord, une fonction normale:

scala> def modN(n: Int, x: Int) = ((x % n) == 0)
scala> modN(5, _ : Int)
res0: Int => Boolean = <function1>

Nous obtenons donc un <function1> partiellement appliqué qui prend un Int, car nous lui avons déjà donné le premier entier. Jusqu'ici tout va bien. Passons maintenant au currying:

scala> def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

Avec cette notation, vous vous attendriez naïvement au travail suivant:

scala> modNCurried(5)
<console>:9: error: missing arguments for method modN;
follow this method with `_' if you want to treat it as a partially applied function
          modNCurried(5)

Ainsi, la notation à liste de paramètres multiples ne semble pas vraiment créer une fonction curry immédiatement (en supposant d'éviter des frais généraux inutiles), mais attend que vous indiquiez explicitement que vous le voulez au curry (la notation a un certain autres avantages aussi):

scala> modNCurried(5) _
res24: Int => Boolean = <function1>

Ce qui est exactement la même chose que nous avons eu avant, donc aucune différence ici, sauf pour la notation. Un autre exemple:

scala> modN _
res35: (Int, Int) => Boolean = <function2>

scala> modNCurried _
res36: Int => (Int => Boolean) = <function1>

Ceci montre comment l’application partielle d’une fonction "normale" donne une fonction qui prend tous les paramètres, alors que l’application partielle d’une fonction avec plusieurs listes de paramètres crée une chaîne de fonctions une par liste de paramètres qui retournent tous une nouvelle fonction:

scala> def foo(a:Int, b:Int)(x:Int)(y:Int) = a * b + x - y
scala> foo _
res42: (Int, Int) => Int => (Int => Int) = <function2>

scala> res42(5)
<console>:10: error: not enough arguments for method apply: (v1: Int, v2: Int)Int => (Int => Int) in trait Function2.
Unspecified value parameter v2.

Comme vous pouvez le constater, comme la première liste de paramètres de foo a deux paramètres, la première fonction de la chaîne en forme de curry possède deux paramètres.


En résumé, les fonctions partiellement appliquées ne sont pas vraiment différentes des fonctions curryed en termes de fonctionnalité. Cela se vérifie facilement étant donné que vous pouvez convertir n’importe quelle fonction en fonction curry:

scala> (modN _).curried
res45: Int => (Int => Boolean) = <function1

scala> modNCurried _
res46: Int => (Int => Boolean) = <function1>

Post Scriptum

Remarque: La raison pour laquelle votre exemple println(filter(nums, modN(2)) fonctionne sans le trait de soulignement après modN(2) semble être que le Scala _ Le compilateur suppose simplement que le soulignement est une commodité pour le programmeur.


Addition: Comme @asflierl l'a correctement souligné, Scala ne semble pas pouvoir déduire le type lors de l'application partielle " "normales":

scala> modN(5, _)
<console>:9: error: missing parameter type for expanded function ((x$1) => modN(5, x$1))

Considérant que cette information est disponible pour les fonctions écrites en utilisant plusieurs notations de liste de paramètres:

scala> modNCurried(5) _
res3: Int => Boolean = <function1>

Ceci répond montre en quoi cela peut être très utile.

87
fresskoma

Le currying a à voir avec les tuples: tourner une fonction qui prend un argument de Tuple en un argument qui prend n arguments séparés, et vice versa En gardant cela à l'esprit, il est essentiel de distinguer le curry de l'application partielle, même dans les langues ne supportant pas proprement le curry.

curry :: ((a, b) -> c) -> a -> b -> c 
   -- curry converts a function that takes all args in a Tuple
   -- into one that takes separate arguments

uncurry :: (a -> b -> c) -> (a, b) -> c
   -- uncurry converts a function of separate args into a function on pairs.

L'application partielle est la possibilité de appliquer une fonction à certains arguments, ce qui donne une nouvelle fonction pour les arguments restants.

Il est facile de se souvenir si vous pensez simplement que le currying est la transformation à faire avec les n-uplets.

Dans les langues utilisées par défaut (comme Haskell), la différence est claire: vous devez réellement faire quelque chose pour passer des arguments dans un Tuple. Mais la plupart des autres langues, y compris Scala, ne sont pas pressées par défaut - tous les arguments sont passés en tant que tuples, ainsi curry/uncurry est beaucoup moins utile et moins évident. Et les gens finissent même par penser que l'application partielle et le curry sont la même chose, simplement parce qu'ils ne peuvent pas représenter facilement des fonctions au curry!

19
Don Stewart

Fonction multivariable:

def modN(n: Int, x: Int) = ((x % n) == 0)

Currying (ou la fonction curry):

def modNCurried(n: Int)(x: Int) = ((x % n) == 0)

Donc, ce n'est pas une fonction partiellement appliquée qui est comparable au curry. C'est la fonction multivariable . Ce qui est comparable à la fonction partiellement appliquée est le résultat de l'appel d'une fonction curry, qui est une fonction avec la même liste de paramètres que la fonction partiellement appliquée.

2
lcn

Juste pour clarifier le dernier point 

Addition: Comme l'a souligné correctement @asflierl, Scala ne semble pas pour pouvoir déduire le type en appliquant partiellement "normal" les fonctions:

Scala peut déduire des types si tous les paramètres sont des caractères génériques, mais pas si certains d'entre eux sont spécifiés et d'autres non.

scala> modN(_,_)
res38: (Int, Int) => Boolean = <function2>

scala> modN(1,_)
<console>:13: error: missing parameter type for expanded function ((x$1) => modN(1, x$1))
       modN(1,_)
              ^
0
Sud