Scala Language
Currying
Suche…
Syntax
- aFunction (10) _ // Using '_' Sagt dem Compiler, dass alle Parameter in den restlichen Parametergruppen aktuell sind.
- nArityFunction.curried // Konvertiert eine N-Arity-Funktion in eine entsprechende aktuelle Version
- anotherFunction (x) (_: String) (z) // Ein beliebiger Parameter wird geändert. Es muss der Typ ausdrücklich angegeben werden.
Ein konfigurierbarer Multiplikator als Curryfunktion
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
Mehrere Parametergruppen verschiedener Typen, aktuelle Parameter für beliebige Positionen
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"
Eine Funktion mit einer einzelnen Parametergruppe erstellen
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
Definieren wir eine Funktion von 2 Argumenten:
def add: (Int, Int) => Int = (x,y) => x + y val three = add(1,2)
Currying add
verwandelt es in eine Funktion, die ein Int
und eine Funktion zurückgibt (von einem Int
zu einem 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)
Sie können dieses Konzept auf jede Funktion anwenden, die mehrere Argumente akzeptiert. Das Currying einer Funktion, die mehrere Argumente übernimmt, wandelt sie in eine Reihe von Anwendungen von Funktionen um, die ein Argument annehmen:
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 nach Wikipedia ,
ist die Technik zum Übersetzen der Auswertung einer Funktion, die mehrere Argumente zur Auswertung einer Funktionsfolge benötigt.
Konkret bedeutet dies im Zusammenhang mit Scala-Typen im Zusammenhang mit einer Funktion, die zwei Argumente enthält (die Arität 2 hat) die Konvertierung von
val f: (A, B) => C // a function that takes two arguments of type `A` and `B` respectively
// and returns a value of type `C`
zu
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`
Für arity-2-Funktionen können wir die Curry-Funktion folgendermaßen schreiben:
def curry[A, B, C](f: (A, B) => C): A => B => C = {
(a: A) => (b: B) => f(a, b)
}
Verwendungszweck:
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 gibt uns einige Sprachfunktionen, die dabei helfen:
- Sie können Curried-Funktionen als Methoden schreiben. so kann
curriedF
geschrieben werden als:
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
- Sie können entladen (dh von
A => B => C
zu(A, B) => C
) gehen, indem Sie eine Standardbibliotheksmethode verwenden:Function.uncurried
val f: (String, Int) => Double = Function.uncurried(curriedF)
f("a", 1) // => 1.0
Wann sollten Sie Currying verwenden?
Currying ist die Technik zum Übersetzen der Auswertung einer Funktion, die mehrere Argumente zur Auswertung einer Funktionsfolge mit jeweils einem einzigen Argument verwendet .
Dies ist normalerweise nützlich, wenn zum Beispiel:
- Verschiedene Argumente einer Funktion werden zu unterschiedlichen Zeiten berechnet. (Beispiel 1)
- Verschiedene Argumente einer Funktion werden nach verschiedenen Ebenen der Anwendung berechnet. (Beispiel 2)
Beispiel 1
Nehmen wir an, dass das jährliche Jahreseinkommen eine Funktion ist, die sich aus dem Einkommen und einem Bonus zusammensetzt:
val totalYearlyIncome:(Int,Int) => Int = (income, bonus) => income + bonus
Die aktuelle Version der obigen 2-Arity-Funktion lautet:
val totalYearlyIncomeCurried: Int => Int => Int = totalYearlyIncome.curried
Beachten Sie in der obigen Definition, dass der Typ auch als angesehen / geschrieben werden kann:
Int => (Int => Int)
Nehmen wir an, dass der jährliche Einkommensanteil im Voraus bekannt ist:
val partialTotalYearlyIncome: Int => Int = totalYearlyIncomeCurried(10000)
Und irgendwann ist der Bonus bekannt:
partialTotalYearlyIncome(100)
Beispiel 2
Nehmen wir an, dass die Automobilherstellung die Anwendung von Felgen und Karosserien umfasst:
val carManufacturing:(String,String) => String = (wheels, body) => wheels + body
Diese Teile werden von verschiedenen Fabriken angewendet:
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..")
}
Beachten Sie, dass die CarWheelsFactory
oben die Fahrzeugherstellungsfunktion CarWheelsFactory
und nur die Räder verwendet.
Der Autoherstellungsprozess wird dann die folgende Form annehmen:
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)
Ein echter Einsatz von Currying.
Was wir haben, ist eine Liste mit Kreditkarten, und wir möchten die Prämien für alle Karten berechnen, die das Kreditkartenunternehmen auszahlen muss. Die Prämien selbst hängen von der Gesamtzahl der Kreditkarten ab, so dass das Unternehmen sie entsprechend anpasst.
Wir haben bereits eine Funktion, die die Prämie für eine einzelne Kreditkarte berechnet und die Gesamtzahl der von der Firma ausgegebenen Karten berücksichtigt:
case class CreditCard(creditInfo: CreditCardInfo, issuer: Person, account: Account)
object CreditCard {
def getPremium(totalCards: Int, creditCard: CreditCard): Double = { ... }
}
Ein vernünftiger Ansatz für dieses Problem wäre nun, jede Kreditkarte einer Prämie zuzuordnen und auf eine Summe zu reduzieren. Etwas wie das:
val creditCards: List[CreditCard] = getCreditCards()
val allPremiums = creditCards.map(CreditCard.getPremium).sum //type mismatch; found : (Int, CreditCard) ⇒ Double required: CreditCard ⇒ ?
Dem Compiler wird dies jedoch nicht gefallen, da CreditCard.getPremium
zwei Parameter erfordert. Teilanwendung zur Rettung! Wir können die Gesamtanzahl der Kreditkarten teilweise anwenden und diese Funktion verwenden, um die Kreditkarten ihren Prämien zuzuordnen. Wir müssen nur die getPremium
Funktion curry getPremium
, indem wir sie so ändern, dass mehrere Parameterlisten verwendet werden, und wir können getPremium
.
Das Ergebnis sollte ungefähr so aussehen:
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