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:

  1. 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 _
  1. 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:

  1. verschillende argumenten van een functie worden op verschillende tijdstippen berekend. (Voorbeeld 1)
  2. 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


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow