Scala Language
Карринг
Поиск…
Синтаксис
- 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 дает нам несколько функций языка, которые помогают в этом:
- Вы можете писать валютные функции как методы. так что
curriedF
можно записать как:
def curriedFAsAMethod(str: String)(int: Int): Double = 1.0
val curriedF = curriedFAsAMethod _
- Вы можете 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)
- различные аргументы функции вычисляются разными уровнями приложения . (Пример 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