Sök…


Syntax

  • aFunction (10) _ // Använda '_' Berättar för kompilatorn att alla parametrar i resten av parametergrupperna kommer att curry.
  • nArityFunction.curried // Konverterar en n-arity-funktion till en ekvivalent curried-version
  • anotherFunction (x) (_: String) (z) // Currying en godtycklig parameter. Den behöver sin typ uttryckligen anges.

En konfigurerbar multiplikator som en curry-funktion

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

Flera parametergrupper av olika typer, curryparametrar för godtyckliga positioner

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 av en funktion med en enda parametergrupp

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

Låt oss definiera en funktion av två argument:

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

Currying add omvandlar den till en funktion som tar en Int och returnerar en funktion (från en Int till en 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)

Du kan tillämpa detta koncept på alla funktioner som tar flera argument. Currying av en funktion som tar flera argument, omvandlar den till en serie applikationer av funktioner som tar ett 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, enligt Wikipedia ,

är tekniken för att översätta utvärderingen av en funktion som tar flera argument för att utvärdera en sekvens av funktioner.

Konkret, när det gäller skalatyper, i samband med en funktion som tar två argument, (har arity 2) är det omvandlingen av

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

till

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`

Så för arity-2-funktioner kan vi skriva curryfunktionen som:

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

Användande:

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 ger oss några språkfunktioner som hjälper till med detta:

  1. Du kan skriva curried-funktioner som metoder. så curriedF kan skrivas som:
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
  1. Du kan ta bort curry (dvs. gå från A => B => C till (A, B) => C ) med hjälp av en standardbiblioteksmetod: Function.uncurried
val f: (String, Int) => Double = Function.uncurried(curriedF)
f("a", 1) // => 1.0

När man ska använda Currying

Currying är tekniken för att översätta utvärderingen av en funktion som tar flera argument för att utvärdera en sekvens av funktioner, var och en med ett enda argument .

Detta är normalt användbart när till exempel:

  1. olika argument för en funktion beräknas vid olika tidpunkter . (Exempel 1)
  2. olika argument för en funktion beräknas med olika nivåer i applikationen . (Exempel 2)

Exempel 1

Låt oss anta att den totala årliga inkomsten är en funktion som består av inkomsten och en bonus:

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

Den curried versionen av ovanstående 2-arity-funktion är:

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

Observera i ovanstående definition att typen också kan ses / skrivas som:

Int => (Int => Int)

Låt oss anta att den årliga inkomstdelen är känd i förväg:

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

Och någon gång i raden är bonusen känd:

partialTotalYearlyIncome(100)

Exempel 2

Låt oss anta att biltillverkningen innebär applicering av bilhjul och bilkarosser:

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

Dessa delar används av olika fabriker:

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..")
}

Observera att CarWheelsFactory ovan karrierar biltillverkningsfunktionen och endast tillämpar hjulen.

Sedan kommer biltillverkningsprocessen att ha följande form:

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)

En verklig användning av Currying.

Det vi har är en lista över kreditkort och vi vill beräkna premierna för alla de kort som kreditkortsföretaget måste betala ut. Premierna själva beror på det totala antalet kreditkort, så att företaget justerar dem i enlighet därmed.

Vi har redan en funktion som beräknar premien för ett enda kreditkort och tar hänsyn till det totala kortet som företaget har utfärdat:

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

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

En rimlig strategi för detta problem skulle nu vara att kartlägga varje kreditkort till en premie och reducera det till en summa. Något som det här:

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

Men kompilatorn kommer inte att gilla detta, eftersom CreditCard.getPremium kräver två parametrar. Delvis tillämpning för att rädda! Vi kan delvis tillämpa det totala antalet kreditkort och använda den funktionen för att kartlägga kreditkorten till deras premier. Allt vi behöver göra är att curry getPremium funktionen genom att ändra den för att använda flera parameterlistor och vi är bra att gå.

Resultatet ska se ut så här:

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow