Szukaj…


Składnia

  • aFunkcja (10) _ // Używanie „_” Informuje kompilator, że wszystkie parametry w pozostałych grupach parametrów będą curry.
  • nArityFunction.curried // Konwertuje funkcję n-arity na równoważną wersję curry
  • anotherFunction (x) (_: String) (z) // Currying dowolnego parametru. Potrzebuje wyraźnie określonego typu.

Konfigurowalny mnożnik jako funkcja 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

Wiele grup parametrów różnych typów, curry parametrów dowolnych pozycji

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 funkcję z jedną grupą parametrów

def minus(left: Int, right: Int) = left - right

val numberMinus5 = minus(_: Int, 5)
val fiveMinusNumber = minus(5, _: Int)

numberMinus5(7)    //  2
fiveMinusNumber(7) // -2

Curry

Zdefiniujmy funkcję 2 argumentów:

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

Curry add przekształca go w funkcję, która pobiera jedną Int i zwraca funkcję (z jednej Int do 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)

Możesz zastosować tę koncepcję do dowolnej funkcji, która pobiera wiele argumentów. Currying funkcję, która pobiera wiele argumentów, przekształca ją w szereg aplikacji funkcji, które pobierają jeden 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)

Curry

Curry, zgodnie z Wikipedią ,

jest techniką tłumaczenia oceny funkcji, która bierze wiele argumentów do oceny sekwencji funkcji.

Konkretnie, jeśli chodzi o typy scala, w kontekście funkcji, która przyjmuje dwa argumenty (ma arity 2), jest to konwersja

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

do

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`

Tak więc dla funkcji arity-2 możemy zapisać funkcję curry jako:

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

Stosowanie:

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 udostępnia nam kilka funkcji językowych, które pomagają w tym:

  1. Możesz pisać funkcje curry jako metody. więc curriedF można zapisać jako:
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
  1. Możesz cofnąć curry (tzn. Przejść z A => B => C do (A, B) => C ) przy użyciu standardowej metody bibliotecznej: Function.uncurried
val f: (String, Int) => Double = Function.uncurried(curriedF)
f("a", 1) // => 1.0

Kiedy stosować curry

Curry to technika tłumaczenia oceny funkcji, która bierze wiele argumentów do oceny sekwencji funkcji, każda z jednym argumentem .

Jest to zwykle przydatne, gdy na przykład:

  1. różne argumenty funkcji są obliczane w różnym czasie . (Przykład 1)
  2. różne argumenty funkcji są obliczane przez różne warstwy aplikacji . (Przykład 2)

Przykład 1

Załóżmy, że całkowity roczny dochód jest funkcją składającą się z dochodu i premii:

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

Curry wersja powyższej funkcji 2-arity to:

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

Należy zauważyć w powyższej definicji, że typ można również wyświetlać / zapisywać jako:

Int => (Int => Int)

Załóżmy, że roczna część dochodu jest znana z góry:

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

W pewnym momencie premia jest znana:

partialTotalYearlyIncome(100)

Przykład 2

Załóżmy, że produkcja samochodów obejmuje zastosowanie kół samochodowych i karoserii:

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

Te części są stosowane przez różne fabryki:

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

Zauważ, że CarWheelsFactory powyżej curry funkcję produkcji samochodu i stosuje tylko koła.

Proces produkcji samochodu przyjmie wówczas następującą 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)

Wykorzystanie Curry w prawdziwym świecie.

Mamy listę kart kredytowych i chcielibyśmy obliczyć składki za wszystkie karty, które wystawca karty kredytowej musi wypłacić. Same składki zależą od całkowitej liczby kart kredytowych, dzięki czemu firma odpowiednio je dostosowuje.

Mamy już funkcję, która oblicza składkę za pojedynczą kartę kredytową i bierze pod uwagę całkowitą liczbę wydanych przez firmę kart:

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

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

Teraz rozsądnym podejściem do tego problemu byłoby zmapowanie każdej karty kredytowej do premii i zmniejszenie jej do sumy. Coś takiego:

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

Jednak kompilatorowi się to nie spodoba, ponieważ CreditCard.getPremium wymaga dwóch parametrów. Częściowe zastosowanie na ratunek! Możemy częściowo zastosować całkowitą liczbę kart kredytowych i użyć tej funkcji do mapowania kart kredytowych na ich składki. Wszystko, co musimy zrobić, to getPremium funkcję getPremium , zmieniając ją tak, aby korzystała z list wielu parametrów i jesteśmy getPremium .

Wynik powinien wyglądać mniej więcej tak:

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
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow