Scala Language
Currying
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:
- Vous pouvez écrire des fonctions au curry en tant que méthodes. Donc
curriedF
peut être écrit commecurriedF
:
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
- 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:
- différents arguments d'une fonction sont calculés à des moments différents . (Exemple 1)
- 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