web-dev-qa-db-fra.com

Utilisation de fonctions partielles dans Scala - comment ça marche?

Je suis nouveau sur Scala, j'utilise 2.9.1 et j'essaie de comprendre comment utiliser les fonctions partielles. J'ai une compréhension de base des fonctions au curry, et je sais que les fonctions partielles sont un peu comme les fonctions au curry où elles ne sont que 2naires ou certaines. Comme vous pouvez le voir, je suis un peu vert à ce sujet.

Il semble que dans certains cas, comme le filtrage XML, la possibilité de fonctions partielles serait très avantageuse, j'espère donc mieux comprendre comment les utiliser.

J'ai une fonction qui utilise la structure RewriteRule, mais j'en ai besoin pour travailler avec deux arguments, alors que la structure RewriteRule n'en prend qu'un, OR une fonction partielle. Je pense que c'est l'un des cas Je pense que c'est utile.

Tous les conseils, liens, paroles de sagesse, etc. sont les bienvenus!

Jusqu'à présent, les réponses sont excellentes et ont clarifié quelques idées fausses fondamentales que j'ai. Je pense qu'ils expliquent également où je me bats - je pense que peut-être que poster une nouvelle question étant un peu plus spécifique aidera, alors je le ferai aussi.

48
PlexQ

Une fonction partielle est une fonction qui n'est valide que pour un sous-ensemble de valeurs de ces types que vous pourriez lui transmettre. Par exemple:

val root: PartialFunction[Double,Double] = {
  case d if (d >= 0) => math.sqrt(d)
}

scala> root.isDefinedAt(-1)
res0: Boolean = false

scala> root(3)
res1: Double = 1.7320508075688772

Ceci est utile lorsque vous avez quelque chose qui sait vérifier si une fonction est définie ou non. Collectez, par exemple:

scala> List(0.5, -0.2, 4).collect(root)   // List of _only roots which are defined_
res2: List[Double] = List(0.7071067811865476, 2.0)

Ceci pas va vous aider à placer deux arguments là où vous en voulez vraiment un.

En revanche, une fonction partiellement appliquée est une fonction où certains de ses arguments ont déjà été remplis.

def add(i: Int, j: Int) = i + j
val add5 = add(_: Int,5)

Maintenant, vous n'avez besoin que d'un seul argument - la chose à laquelle ajouter 5 - au lieu de deux:

scala> add5(2)
res3: Int = 7

Vous pouvez voir dans cet exemple comment l'utiliser.

Mais si vous devez spécifier ces deux arguments, cela toujours ne le fera pas - disons que vous voulez utiliser map, par exemple, et vous devez lui donner une fonction de un argument, mais vous voulez qu'il ajoute deux choses différentes. Eh bien, alors vous pouvez

val addTupled = (add _).tupled

qui appliquera partiellement la fonction (vraiment, il suffit de créer une fonction à partir de la méthode, car rien n'a été rempli), puis de combiner les arguments séparés dans un Tuple. Vous pouvez maintenant l'utiliser dans des endroits qui nécessitent un seul argument (en supposant que le type est correct):

scala> List((1,2), (4,5), (3,8)).map(addTupled)
res4: List[Int] = List(3, 9, 11)

En revanche, curry est encore différent; il transforme les fonctions de la forme (A,B) => C en A => B => C. Autrement dit, étant donné une fonction de plusieurs arguments, il produira une chaîne de fonctions qui prennent chacune un argument et renvoient une chaîne plus courte (vous pouvez la considérer comme appliquant partiellement un argument à la fois).

val addCurried = (add _).curried

scala> List(1,4,3).map(addCurried)
res5: List[Int => Int] = List(<function1>, <function1>, <function1>)

scala> res5.head(2)   // is the first function, should add 1
res6: Int = 3

scala> res5.tail.head(5)   // Second function should add 4
res7: Int = 9

scala> res5.last(8)  // Third function should add 3
res8: Int = 11
142
Rex Kerr

L'explication de Rex Kerr est très bonne - et aucune surprise là non plus. La question est clairement de confondre fonctions partielles avec fonctions partiellement appliquées. Pour tout ce que ça vaut, j'ai fait la même confusion moi-même quand j'ai appris la Scala.

Cependant, comme la question attire l'attention sur les fonctions partielles, je voudrais en parler un peu.

Beaucoup de gens disent que les fonctions partielles sont des fonctions qui ne sont pas définies pour toutes les entrées, et c'est vrai pour les mathématiques, mais pas pour Scala. Dans Scala, une fonction peut ne pas être définie pour toutes les entrées non plus. En fait, puisque la fonction partielle hérite de la fonction, alors la fonction inclut également toutes les fonctions partielles, ce qui rend cela inévitable.

D'autres mentionnent la méthode isDefinedAt, qui est, en effet, une différence, mais surtout sur l'implémentation. C'est tellement vrai que Scala 2.10 sera probablement publié avec une "fonction partielle rapide", qui ne dépend pas de isDefinedAt.

Et quelques personnes impliquent même que la méthode apply pour les fonctions partielles fait quelque chose de différent de la méthode apply pour les fonctions, comme l'exécution uniquement pour l'entrée qui est définie - qui ne pourrait pas être plus éloignée de la vérité. La méthode apply est exactement la même.

Les fonctions partielles se résument vraiment à une autre méthode: orElse. Cela résume bien mieux tous les cas d'utilisation des fonctions partielles que isDefinedAt, car les fonctions partielles consistent en réalité à faire l'une des choses suivantes:

  • Enchaînant des fonctions partielles (c'est ce que fait orElse), de sorte qu'une entrée sera essayée sur chaque fonction partielle jusqu'à ce que l'une d'elles corresponde.
  • Faire quelque chose de différent si une fonction partielle ne correspond pas, au lieu de lever une exception, ce qui se produirait si vous enchaîniez cette chose différente à l'aide de orElse.

Je ne dis pas que tout peut être facilement implémenté en termes de orElse, attention. Je dis simplement que les fonctions partielles consistent à faire autre chose lorsqu'une entrée n'est pas définie pour elle.

35
Daniel C. Sobral