Scala Language
Currying
Zoeken…
Syntaxis
- aFunction (10) _ // Using '_' Vertelt de compiler dat alle parameters in de rest van de parametergroepen worden gecurried.
- nArityFunction.curried // Converteert een n-arity-functie naar een equivalente gecurryde versie
- anotherFunction (x) (_: String) (z) // Een willekeurige parameter curreren. Het moet zijn type expliciet vermelden.
Een configureerbare multiplier als een curried-functie
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
Meerdere parametergroepen van verschillende typen, curryparameters van willekeurige posities
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"
Een functie samenstellen met een enkele parametergroep
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
Laten we een functie van 2 argumenten definiëren:
def add: (Int, Int) => Int = (x,y) => x + y val three = add(1,2)
Currying add
transformeert het in een functie die één Int
en een functie retourneert (van één Int
naar een 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)
U kunt dit concept toepassen op elke functie waarvoor meerdere argumenten nodig zijn. Een functie waarvoor meerdere argumenten nodig zijn, wordt omgezet in een reeks toepassingen met functies waarvoor één argument nodig is:
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 volgens Wikipedia ,
is de techniek van het vertalen van de evaluatie van een functie waarvoor meerdere argumenten nodig zijn om een reeks functies te evalueren.
Concreet, in termen van scalatypes, in de context van een functie die twee argumenten aanneemt, (heeft arity 2) is het de conversie van
val f: (A, B) => C // a function that takes two arguments of type `A` and `B` respectively
// and returns a value of type `C`
naar
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`
Dus voor arity-2-functies kunnen we de curry-functie schrijven als:
def curry[A, B, C](f: (A, B) => C): A => B => C = {
(a: A) => (b: B) => f(a, b)
}
Gebruik:
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 geeft ons een paar taalfuncties die hierbij helpen:
- U kunt gecurryde functies als methoden schrijven. dus
curriedF
kan worden geschreven als:
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
- U kunt de curry ongedaan maken (bijv. Van
A => B => C
naar(A, B) => C
) met behulp van een standaardbibliotheekmethode:Function.uncurried
val f: (String, Int) => Double = Function.uncurried(curriedF)
f("a", 1) // => 1.0
Wanneer Currying gebruiken?
Currying is de techniek van het vertalen van de evaluatie van een functie waarbij meerdere argumenten worden gebruikt om een reeks functies te evalueren, elk met een enkel argument .
Dit is normaal handig wanneer bijvoorbeeld:
- verschillende argumenten van een functie worden op verschillende tijdstippen berekend. (Voorbeeld 1)
- verschillende argumenten van een functie worden berekend door verschillende lagen van de toepassing . (Voorbeeld 2)
voorbeeld 1
Laten we aannemen dat het totale jaarlijkse inkomen een functie is die bestaat uit het inkomen en een bonus:
val totalYearlyIncome:(Int,Int) => Int = (income, bonus) => income + bonus
De gecurryde versie van de bovenstaande 2-arity-functie is:
val totalYearlyIncomeCurried: Int => Int => Int = totalYearlyIncome.curried
Merk in de bovenstaande definitie op dat het type ook kan worden bekeken / geschreven als:
Int => (Int => Int)
Laten we aannemen dat het jaarlijkse inkomstengedeelte van tevoren bekend is:
val partialTotalYearlyIncome: Int => Int = totalYearlyIncomeCurried(10000)
En op een gegeven moment is de bonus bekend:
partialTotalYearlyIncome(100)
Voorbeeld 2
Laten we aannemen dat de autofabricage de toepassing van autowielen en carrosserie omvat:
val carManufacturing:(String,String) => String = (wheels, body) => wheels + body
Deze onderdelen worden toegepast door verschillende fabrieken:
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..")
}
Merk op dat de CarWheelsFactory
boven de auto-vervaardigingsfunctie draait en alleen de wielen toepast.
Het productieproces van de auto zal dan de volgende vorm aannemen:
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)
Een echt gebruik van Currying.
We hebben een lijst met creditcards en we willen graag de premies berekenen voor al die kaarten die het creditcardbedrijf moet betalen. De premies zelf zijn afhankelijk van het totale aantal creditcards, zodat het bedrijf ze dienovereenkomstig aanpast.
We hebben al een functie die de premie voor een enkele creditcard berekent en rekening houdt met het totale aantal kaarten dat het bedrijf heeft uitgegeven:
case class CreditCard(creditInfo: CreditCardInfo, issuer: Person, account: Account)
object CreditCard {
def getPremium(totalCards: Int, creditCard: CreditCard): Double = { ... }
}
Nu zou een redelijke benadering van dit probleem zijn om elke creditcard aan een premie toe te wijzen en tot een som te verlagen. Iets zoals dit:
val creditCards: List[CreditCard] = getCreditCards()
val allPremiums = creditCards.map(CreditCard.getPremium).sum //type mismatch; found : (Int, CreditCard) ⇒ Double required: CreditCard ⇒ ?
De compiler zal dit echter niet leuk vinden, omdat CreditCard.getPremium
twee parameters vereist. Gedeeltelijke toepassing te hulp! We kunnen het totale aantal creditcards gedeeltelijk toepassen en die functie gebruiken om de creditcards toe te wijzen aan hun premies. Het enige dat we moeten doen, is de getPremium
functie getPremium
door deze te wijzigen om meerdere parameterlijsten te gebruiken en we zijn klaar om te beginnen.
Het resultaat zou er ongeveer zo uit moeten zien:
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