Recherche…


Syntaxe

  • aFunction (10) _ // Utilisation de '_' Indique au compilateur que tous les paramètres des autres groupes de paramètres seront curry.
  • nArityFunction.curried // Convertit une fonction n-arity en une version curry équivalente
  • anotherFunction (x) (_: String) (z) // currying un paramètre arbitraire. Son type doit être explicitement indiqué.

Un multiplicateur configurable en tant que fonction curry

def multiply(factor: Int)(numberToBeMultiplied: Int): Int = factor * numberToBeMultiplied

val multiplyBy3 = multiply(3)_     // resulting function signature Int => Int
val multiplyBy10 = multiply(10)_ // resulting function signature Int => Int

val sixFromCurriedCall = multiplyBy3(2) //6
val sixFromFullCall = multiply(3)(2)    //6

val fortyFromCurriedCall = multiplyBy10(4) //40
val fortyFromFullCall = multiply(10)(4)    //40

Plusieurs groupes de paramètres de différents types, paramètres de curry des positions arbitraires

def numberOrCharacterSwitch(toggleNumber: Boolean)(number: Int)(character: Char): String = 
  if (toggleNumber) number.toString else character.toString

// need to explicitly specify the type of the parameter to be curried
// resulting function signature Boolean => String
val switchBetween3AndE = numberOrCharacterSwitch(_: Boolean)(3)('E') 

switchBetween3AndE(true) // "3"
switchBetween3AndE(false) // "E"

Currying une fonction avec un seul groupe de paramètres

def minus(left: Int, right: Int) = left - right

val numberMinus5 = minus(_: Int, 5)
val fiveMinusNumber = minus(5, _: Int)

numberMinus5(7)    //  2
fiveMinusNumber(7) // -2

Currying

Définissons une fonction de 2 arguments:

def add: (Int, Int) => Int = (x,y) => x + y
val three = add(1,2) 

Le curry add transforme en une fonction qui prend un Int et retourne une fonction (d' un Int à un Int )

val addCurried: (Int) => (Int => Int) = add2.curried
//               ^~~ take *one* Int
//                        ^~~~ return a *function* from Int to Int

val add1: Int => Int = addCurried(1)
val three: Int = add1(2)
val allInOneGo: Int = addCurried(1)(2)

Vous pouvez appliquer ce concept à toute fonction qui prend plusieurs arguments. Le curry d'une fonction qui prend plusieurs arguments, le transforme en une série d'applications de fonctions qui prennent un argument:

def add3: (Int, Int, Int) => Int = (a,b,c) => a + b + c + d
def add3Curr: Int => (Int => (Int => Int)) = add3.curried

val x = add3Curr(1)(2)(42)

Currying

Currying, selon Wikipedia ,

est la technique de traduction de l'évaluation d'une fonction qui prend plusieurs arguments pour évaluer une séquence de fonctions.

Concrètement, en termes de types de scala, dans le contexte d’une fonction qui prend deux arguments, (a arité 2), c’est la conversion de

val f: (A, B) => C // a function that takes two arguments of type `A` and `B` respectively 
                   // and returns a value of type `C`

à

val curriedF: A => B => C // a function that take an argument of type `A` 
                          // and returns *a function* 
                          // that takes an argument of type `B` and returns a `C`

Donc, pour les fonctions arity-2, nous pouvons écrire la fonction curry comme:

def curry[A, B, C](f: (A, B) => C): A => B => C = { 
  (a: A) => (b: B) => f(a, b) 
}

Usage:

val f: (String, Int) => Double = {(_, _) => 1.0}
val curriedF: String => Int => Double = curry(f)
f("a", 1)        // => 1.0
curriedF("a")(1) // => 1.0

Scala nous offre quelques fonctionnalités linguistiques qui facilitent ceci:

  1. Vous pouvez écrire des fonctions au curry en tant que méthodes. Donc curriedF peut être écrit comme curriedF :
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
  1. Vous pouvez annuler le curry (c'est-à-dire passer de A => B => C à (A, B) => C ) en utilisant une méthode de bibliothèque standard: Function.uncurried
val f: (String, Int) => Double = Function.uncurried(curriedF)
f("a", 1) // => 1.0

Quand utiliser le curry

Le curry est la technique de traduction de l'évaluation d'une fonction qui prend plusieurs arguments pour évaluer une séquence de fonctions, chacune avec un seul argument .

Ceci est normalement utile lorsque, par exemple:

  1. différents arguments d'une fonction sont calculés à des moments différents . (Exemple 1)
  2. différents arguments d'une fonction sont calculés par différents niveaux de l'application . (Exemple 2)

Exemple 1

Supposons que le revenu annuel total est une fonction composée du revenu et d'une prime:

val totalYearlyIncome:(Int,Int) => Int =  (income, bonus) => income + bonus

La version curry de la fonction 2-arity ci-dessus est:

val totalYearlyIncomeCurried: Int => Int => Int = totalYearlyIncome.curried

Notez dans la définition ci-dessus que le type peut également être vu / écrit comme suit:

Int => (Int => Int)

Supposons que la partie du revenu annuel soit connue à l'avance:

val partialTotalYearlyIncome: Int => Int = totalYearlyIncomeCurried(10000)

Et à un moment donné, le bonus est connu:

partialTotalYearlyIncome(100)

Exemple 2

Supposons que la fabrication de voitures implique l'application de roues de voiture et de carrosserie:

val carManufacturing:(String,String) => String = (wheels, body) => wheels + body

Ces pièces sont appliquées par différentes usines:

class CarWheelsFactory {
  def applyCarWheels(carManufacturing:(String,String) => String): String => String =
          carManufacturing.curried("applied wheels..")
}
    
class CarBodyFactory {
  def applyCarBody(partialCarWithWheels: String => String): String = partialCarWithWheels("applied car body..")
}

Remarquez que la CarWheelsFactory ci-dessus CarWheelsFactory la fonction de fabrication de la voiture et applique uniquement les roues.

Le processus de fabrication de la voiture prendra alors la forme ci-dessous:

val carWheelsFactory = new CarWheelsFactory()
val carBodyFactory   = new CarBodyFactory()

val carManufacturing:(String,String) => String = (wheels, body) => wheels + body
  
val partialCarWheelsApplied: String => String  = carWheelsFactory.applyCarWheels(carManufacturing)
val carCompleted = carBodyFactory.applyCarBody(partialCarWheelsApplied)

Une utilisation réelle du curry.

Nous avons une liste de cartes de crédit et nous aimerions calculer les primes pour toutes les cartes que la compagnie de carte de crédit doit payer. Les primes elles-mêmes dépendent du nombre total de cartes de crédit, de sorte que l’entreprise les ajuste en conséquence.

Nous avons déjà une fonction qui calcule la prime pour une seule carte de crédit et prend en compte le nombre total de cartes émises par l'entreprise:

case class CreditCard(creditInfo: CreditCardInfo, issuer: Person, account: Account)

object CreditCard {
  def getPremium(totalCards: Int, creditCard: CreditCard): Double = { ... }
}

Maintenant, une approche raisonnable de ce problème consisterait à mapper chaque carte de crédit sur une prime et à la réduire à une somme. Quelque chose comme ça:

val creditCards: List[CreditCard] = getCreditCards()
val allPremiums = creditCards.map(CreditCard.getPremium).sum //type mismatch; found : (Int, CreditCard) ⇒ Double required: CreditCard ⇒ ?

Cependant, le compilateur ne va pas aimer cela, car CreditCard.getPremium nécessite deux paramètres. Application partielle à la rescousse! Nous pouvons appliquer partiellement le nombre total de cartes de crédit et utiliser cette fonction pour associer les cartes de crédit à leurs primes. Tout ce que nous devons faire est de curry la fonction getPremium en la changeant pour utiliser plusieurs listes de paramètres et nous sommes getPremium à aller.

Le résultat devrait ressembler à ceci:

object CreditCard {
  def getPremium(totalCards: Int)(creditCard: CreditCard): Double = { ... }
}

val creditCards: List[CreditCard] = getCreditCards()

val getPremiumWithTotal = CreditCard.getPremium(creditCards.length)_

val allPremiums = creditCards.map(getPremiumWithTotal).sum


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow