Ricerca…


Sintassi

  • aFunction (10) _ // Utilizzo di '_' Indica al compilatore che tutti i parametri nel resto dei gruppi di parametri verranno ritentati.
  • nArityFunction.curried // Converte una funzione n-arity in una versione al curry equivalente
  • anotherFunction (x) (_: String) (z) // Esecuzione di un parametro arbitrario. Ha bisogno del suo tipo esplicitamente dichiarato.

Un moltiplicatore configurabile come funzione al 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

Gruppi di parametri multipli di tipi diversi, che calcolano i parametri di posizioni arbitrarie

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"

Esecuzione di una funzione con un singolo gruppo di parametri

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

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

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

accattivarsi

Definiamo una funzione di 2 argomenti:

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

Currying add trasforma in una funzione che prende un Int e restituisce una funzione (da un Int a un 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)

È possibile applicare questo concetto a qualsiasi funzione che richiede più argomenti. Eseguendo una funzione che accetta più argomenti, la trasforma in una serie di applicazioni di funzioni che prendono un argomento:

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)

accattivarsi

Currying, secondo Wikipedia ,

è la tecnica di tradurre la valutazione di una funzione che prende più argomenti per valutare una sequenza di funzioni.

Concretamente, in termini di tipi di scala, nel contesto di una funzione che prende due argomenti, (ha arity 2) è la conversione di

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

a

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`

Quindi per le funzioni di arity-2 possiamo scrivere la funzione curry come:

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

Uso:

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 ci offre alcune funzionalità linguistiche che aiutano con questo:

  1. È possibile scrivere funzioni curry come metodi. così curriedF può essere scritto come:
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
  1. È possibile annullare la ricorrenza (ovvero passare da A => B => C a (A, B) => C ) utilizzando un metodo di libreria standard: Function.uncurried
val f: (String, Int) => Double = Function.uncurried(curriedF)
f("a", 1) // => 1.0

Quando usare Currying

Il Currying è la tecnica per tradurre la valutazione di una funzione che prende più argomenti per valutare una sequenza di funzioni, ognuna con un singolo argomento .

Questo è normalmente utile quando ad esempio:

  1. diversi argomenti di una funzione sono calcolati in momenti diversi . (Esempio 1)
  2. diversi argomenti di una funzione sono calcolati da diversi livelli dell'applicazione . (Esempio 2)

Esempio 1

Supponiamo che il reddito annuale totale sia una funzione composta dal reddito e da un bonus:

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

La versione al curry della suddetta funzione 2-arity è:

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

Nota nella definizione di cui sopra che il tipo può essere anche visualizzato / scritto come:

Int => (Int => Int)

Supponiamo che la porzione di reddito annuale sia nota in anticipo:

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

E ad un certo punto della linea il bonus è noto:

partialTotalYearlyIncome(100)

Esempio 2

Supponiamo che la produzione automobilistica implichi l'applicazione di ruote per auto e carrozzeria:

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

Queste parti sono applicate da diverse fabbriche:

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

Si noti che il CarWheelsFactory sopra CarWheelsFactory la funzione di produzione dell'auto e applica solo le ruote.

Il processo di produzione dell'auto assumerà quindi la seguente forma:

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)

Un uso del mondo reale di Currying.

Quello che abbiamo è un elenco di carte di credito e vorremmo calcolare i premi per tutte quelle carte che la società della carta di credito deve pagare. I premi stessi dipendono dal numero totale di carte di credito, in modo che la società li adegui di conseguenza.

Abbiamo già una funzione che calcola il premio per una singola carta di credito e tiene conto del totale delle carte emesse dalla società:

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

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

Ora un approccio ragionevole a questo problema sarebbe quello di mappare ogni carta di credito a un premio e ridurlo a una somma. Qualcosa come questo:

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

Comunque il compilatore non gradirà questo, perché CreditCard.getPremium richiede due parametri. Applicazione parziale al salvataggio! Possiamo parzialmente applicare il numero totale di carte di credito e utilizzare questa funzione per mappare le carte di credito ai loro premi. Tutto quello che dobbiamo fare è curry la funzione getPremium cambiandola per usare più liste di parametri e siamo a posto.

Il risultato dovrebbe essere simile a questo:

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow