Buscar..


Sintaxis

  • aFunction (10) _ // Using '_' Le dice al compilador que todos los parámetros en el resto de los grupos de parámetros serán procesados.
  • nArityFunction.curried // Convierte una función n-arity a una versión con currículum equivalente
  • anotherFunction (x) (_: String) (z) // Currying un parámetro arbitrario. Necesita su tipo explícitamente establecido.

Un multiplicador configurable como función de 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

Múltiples grupos de parámetros de diferentes tipos, parámetros de posiciones arbitrarias

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 una función con un solo grupo de parámetros

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

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

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

Zurra

Vamos a definir una función de 2 argumentos:

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

La add curry lo transforma en una función que toma un Int y devuelve una función (de 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)

Puede aplicar este concepto a cualquier función que tome múltiples argumentos. El curry de una función que toma múltiples argumentos, la transforma en una serie de aplicaciones de funciones que toman un argumento:

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)

Zurra

Currying, según Wikipedia ,

es la técnica de traducir la evaluación de una función que toma múltiples argumentos para evaluar una secuencia de funciones.

Concretamente, en términos de tipos de escala, en el contexto de una función que toma dos argumentos, (tiene aridad 2) es la conversión de

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`

Así que para las funciones arity-2 podemos escribir la función curry como:

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 nos da algunas características de lenguaje que ayudan con esto:

  1. Puede escribir funciones al curry como métodos. así que curriedF se puede escribir como:
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
  1. Puede deshacer el curry (es decir, pasar de A => B => C a (A, B) => C ) utilizando un método de biblioteca estándar: Function.uncurried
val f: (String, Int) => Double = Function.uncurried(curriedF)
f("a", 1) // => 1.0

Cuando usar Currying

El curry es la técnica de traducir la evaluación de una función que toma múltiples argumentos para evaluar una secuencia de funciones, cada una con un solo argumento .

Esto suele ser útil cuando, por ejemplo:

  1. Diferentes argumentos de una función se calculan en diferentes momentos . (Ejemplo 1)
  2. Los diferentes argumentos de una función se calculan por diferentes niveles de la aplicación . (Ejemplo 2)

Ejemplo 1

Supongamos que el ingreso anual total es una función compuesta por el ingreso y una bonificación:

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

La versión al curry de la función 2-aridad anterior es:

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

Tenga en cuenta en la definición anterior que el tipo también se puede ver / escribir como:

Int => (Int => Int)

Supongamos que la porción del ingreso anual se conoce de antemano:

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

Y en algún punto de la línea se conoce la bonificación:

partialTotalYearlyIncome(100)

Ejemplo 2

Supongamos que la fabricación de automóviles implica la aplicación de ruedas y carrocería de automóviles:

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

Estas piezas son aplicadas por diferentes fábricas:

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

Tenga en cuenta que la CarWheelsFactory anterior aplica la función de fabricación del automóvil y solo aplica las ruedas.

El proceso de fabricación del automóvil tomará la siguiente 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 mundo real de Currying.

Lo que tenemos es una lista de tarjetas de crédito y nos gustaría calcular las primas de todas las tarjetas que la compañía de tarjetas de crédito tiene que pagar. Las primas dependen del número total de tarjetas de crédito, por lo que la empresa las ajusta en consecuencia.

Ya tenemos una función que calcula la prima de una sola tarjeta de crédito y tiene en cuenta el total de tarjetas que la compañía ha emitido:

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

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

Ahora, un enfoque razonable para este problema sería asignar cada tarjeta de crédito a una prima y reducirla a una suma. Algo como esto:

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

Sin embargo, al compilador no le va a gustar esto, porque CreditCard.getPremium requiere dos parámetros. Aplicación parcial al rescate! Podemos aplicar parcialmente el número total de tarjetas de crédito y usar esa función para asignar las tarjetas de crédito a sus primas. Todo lo que necesitamos hacer es curry la función getPremium cambiándola para usar múltiples listas de parámetros y estamos getPremium .

El resultado debe verse algo como esto:

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow