Поиск…


Синтаксис

  • aFunction (10) _ // Использование '_' Сообщает компилятору, что все параметры в остальных группах параметров будут зависеть.
  • nArityFunction.curried // Преобразует функцию n-arity в эквивалентную версию в карри
  • anotherFunction (x) (_: String) (z) // Выполнение произвольного параметра. Он явно требует своего типа.

Конфигурируемый множитель в виде каррической функции

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

Несколько групп параметров разных типов, параметры каррирования произвольных позиций

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"

Вычисление функции с помощью одной группы параметров

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

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

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

Карринг

Определим функцию из двух аргументов:

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

Currying add преобразует его в функцию, которая принимает один Int и возвращает функцию (от одного Int до 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)

Вы можете применить это понятие к любой функции, которая принимает несколько аргументов. Вычисляя функцию, которая принимает несколько аргументов, преобразует ее в ряд приложений функций, которые принимают один аргумент:

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)

Карринг

Карри, согласно Википедии ,

является методом перевода оценки функции, которая принимает несколько аргументов при оценке последовательности функций.

Конкретно, в терминах типов scala, в контексте функции, которая принимает два аргумента (имеет смысл 2), это преобразование

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

в

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`

Итак, для функций arity-2 мы можем написать функцию curry как:

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

Использование:

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 дает нам несколько функций языка, которые помогают в этом:

  1. Вы можете писать валютные функции как методы. так что curriedF можно записать как:
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
  1. Вы можете un-curry (т.е. перейти от A => B => C к (A, B) => C ) с помощью стандартного библиотечного метода: Function.uncurried
val f: (String, Int) => Double = Function.uncurried(curriedF)
f("a", 1) // => 1.0

Когда использовать Currying

Currying - это метод перевода оценки функции, которая принимает несколько аргументов в оценку последовательности функций, каждая из которых имеет один аргумент .

Это обычно полезно, например, когда:

  1. различные аргументы функции вычисляются в разное время . (Пример 1)
  2. различные аргументы функции вычисляются разными уровнями приложения . (Пример 2)

Пример 1

Предположим, что общий годовой доход - это функция, состоящая из дохода и бонуса:

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

Версия с установленной версией 2-arity:

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

Обратите внимание, что в приведенном выше определении тип также можно просмотреть / записать как:

Int => (Int => Int)

Предположим, что годовая часть дохода известна заранее:

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

И в какой-то момент вниз по линии бонус известен:

partialTotalYearlyIncome(100)

Пример 2.

Предположим, что производство автомобилей связано с применением автомобильных колес и кузова автомобиля:

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

Эти части применяются разными фабриками:

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

Обратите внимание, что CarWheelsFactory над CarWheelsFactory выполняет функцию изготовления автомобилей и применяет только колеса.

Затем процесс производства автомобилей примет следующую форму:

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)

Реальное использование Карри.

У нас есть список кредитных карт, и мы хотели бы рассчитать страховые взносы для всех тех карт, которые компания кредитной карты должна выплатить. Сами премии зависят от общего количества кредитных карт, поэтому компания соответствующим образом корректирует их.

У нас уже есть функция, которая рассчитывает премию за одну кредитную карту и учитывает общие карты, выпущенные компанией:

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

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

Теперь разумным подходом к этой проблеме будет сопоставление каждой кредитной карты с премией и ее сокращение до суммы. Что-то вроде этого:

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

Однако компилятору это не понравится, потому что CreditCard.getPremium требует два параметра. Частичное приложение для спасения! Мы можем частично применить общее количество кредитных карт и использовать эту функцию для сопоставления кредитных карт с их премиями. Все, что нам нужно сделать, это каррировать функцию getPremium , изменив ее, чтобы использовать несколько списков параметров, и нам хорошо идти.

Результат должен выглядеть примерно так:

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
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow