Я видел ссылки на функции карри в нескольких статьях и блогах, но не могу найти хорошее объяснение (или хотя бы одно, которое имеет смысл!)
add x y = x+y
(curried) отличается от add (x, y)=x+y
(uncurried)
Я видел ссылки на функции карри в нескольких статьях и блогах, но не могу найти хорошее объяснение (или хотя бы одно, которое имеет смысл!)
add x y = x+y
(curried) отличается от add (x, y)=x+y
(uncurried)
Ответы:
Каррирование - это когда вы разбиваете функцию, которая принимает несколько аргументов, на ряд функций, каждая из которых принимает только один аргумент. Вот пример в JavaScript:
function add (a, b) {
return a + b;
}
add(3, 4); // returns 7
Это функция, которая принимает два аргумента, a и b, и возвращает их сумму. Теперь мы будем карри эту функцию:
function add (a) {
return function (b) {
return a + b;
}
}
Это функция, которая принимает один аргумент, a, и возвращает функцию, которая принимает другой аргумент, b, и эта функция возвращает их сумму.
add(3)(4);
var add3 = add(3);
add3(4);
Первый оператор возвращает 7, как оператор add (3, 4). Второе утверждение определяет новую функцию с именем add3, которая добавит 3 к своему аргументу. Это то, что некоторые люди могут назвать закрытием. Третье утверждение использует операцию add3 для добавления 3 к 4, в результате чего снова получается 7.
[1, 2, 3, 4, 5]
который вы хотите умножить на произвольное число. В Haskell я могу написать, map (* 5) [1, 2, 3, 4, 5]
чтобы умножить весь список 5
, и таким образом генерировать список [5, 10, 15, 20, 25]
.
map
должна быть функция, которая принимает только 1 аргумент - элемент из списка. Умножение - как математическая концепция - является бинарной операцией; требуется 2 аргумента. Однако в Haskell *
есть функция карри, аналогичная второй версии add
в этом ответе. Результатом (* 5)
является функция, которая принимает один аргумент и умножает его на 5, что позволяет нам использовать его с картой.
В алгебре функций иметь дело с функциями, которые принимают несколько аргументов (или эквивалентный один аргумент, который является N-кортежем), несколько неэлегантно, но, как доказал Моисей Шенфинкель (и, независимо, Хаскелл Карри), это не нужно: все, что вам нужно нужны функции, которые принимают один аргумент.
Итак, как вы справляетесь с тем, что, естественно, выражаете, скажем f(x,y)
,? Ну, вы воспринимаете это как эквивалент f(x)(y)
- f(x)
, вызываете его g
, это функция, и вы применяете эту функцию к y
. Другими словами, у вас есть только функции, которые принимают один аргумент - но некоторые из этих функций возвращают другие функции (которые также принимают один аргумент ;-).
Как обычно, в Википедии есть хорошая сводка об этом, с множеством полезных указателей (вероятно, включая те, которые касаются ваших любимых языков ;-), а также немного более строгой математической обработки.
div :: Integral a => a -> a -> a
- обратите внимание на эти множественные стрелки? «Отобразить a на функцию, отображающую a на a» - одно чтение ;-) Вы могли бы использовать (единственный) аргумент кортежа для div
& c, но это было бы действительно не-идиоматично в Haskell.
Вот конкретный пример:
Предположим, у вас есть функция, которая вычисляет гравитационную силу, действующую на объект. Если вы не знаете формулу, вы можете найти ее здесь . Эта функция принимает в качестве аргументов три необходимых параметра.
Теперь, находясь на земле, вам нужно только рассчитать силы для объектов на этой планете. На функциональном языке вы можете передать массу земли функции и затем частично оценить ее. То, что вы получите, - это еще одна функция, которая принимает только два аргумента и вычисляет гравитационную силу объектов на земле. Это называется карри.
Карринг - это преобразование, которое можно применять к функциям, чтобы они могли принимать на один аргумент меньше, чем ранее.
Например, в F # вы можете определить функцию следующим образом:
let f x y z = x + y + z
Здесь функция f принимает параметры x, y и z и суммирует их вместе так:
f 1 2 3
Возвращает 6.
Поэтому из нашего определения мы можем определить функцию карри для f: -
let curry f = fun x -> f x
Где fun x -> fx - лямбда-функция, эквивалентная x => f (x) в C #. Эта функция вводит функцию, которую вы хотите каррировать, и возвращает функцию, которая принимает один аргумент и возвращает указанную функцию с первым аргументом, установленным для входного аргумента.
Используя наш предыдущий пример, мы можем получить карри f таким образом:
let curryf = curry f
Затем мы можем сделать следующее:
let f1 = curryf 1
Который дает нам функцию f1, которая эквивалентна f1 yz = 1 + y + z. Это означает, что мы можем сделать следующее:
f1 2 3
Который возвращает 6.
Этот процесс часто путают с «частичным применением функции», которое можно определить следующим образом:
let papply f x = f x
Хотя мы можем расширить его до нескольких параметров, а именно:
let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.
Частичное приложение будет принимать функцию и параметр (ы) и возвращать функцию, для которой требуется один или несколько меньших параметров, и, как показывают предыдущие два примера, реализовано непосредственно в стандартном определении функции F #, чтобы мы могли достичь предыдущего результата, таким образом:
let f1 = f 1
f1 2 3
Который вернет результат 6.
В заключении:-
Разница между карри и частичным применением функции заключается в том, что:
Карринг берет функцию и предоставляет новую функцию, принимающую один аргумент и возвращающую указанную функцию с первым аргументом, установленным для этого аргумента. Это позволяет нам представлять функции с несколькими параметрами в виде серии функций с одним аргументом . Пример:-
let f x y z = x + y + z
let curryf = curry f
let f1 = curryf 1
let f2 = curryf 2
f1 2 3
6
f2 1 3
6
Частичное применение функции является более прямым - оно принимает функцию и один или несколько аргументов и возвращает функцию с первыми n аргументами, для которых задано n аргументов. Пример:-
let f x y z = x + y + z
let f1 = f 1
let f2 = f 2
f1 2 3
6
f2 1 3
6
Это может быть способ использовать функции для создания других функций.
В JavaScript:
let add = function(x){
return function(y){
return x + y
};
};
Позвольте нам назвать это так:
let addTen = add(10);
Когда это выполняется, 10
передается как x
;
let add = function(10){
return function(y){
return 10 + y
};
};
что означает, что нам возвращается эта функция:
function(y) { return 10 + y };
Поэтому, когда вы звоните
addTen();
Вы действительно звоните:
function(y) { return 10 + y };
Итак, если вы сделаете это:
addTen(4)
это так же, как:
function(4) { return 10 + 4} // 14
Таким образом, наша addTen()
команда всегда добавляет десять к тому, что мы передаем. Мы можем создавать аналогичные функции одним и тем же способом:
let addTwo = add(2) // addTwo(); will add two to whatever you pass in
let addSeventy = add(70) // ... and so on...
Теперь очевидный последующий вопрос заключается в том, почему на земле вы когда-нибудь хотели бы это сделать? Он превращает то, что было нетерпеливой операцией, x + y
в операцию, которую можно выполнить лениво, что означает, что мы можем сделать, по крайней мере, две вещи: 1. кэшировать дорогие операции; 2. достичь абстракций в функциональной парадигме.
Представьте, что наша функция карри выглядит так:
let doTheHardStuff = function(x) {
let z = doSomethingComputationallyExpensive(x)
return function (y){
z + y
}
}
Мы могли бы вызвать эту функцию один раз, а затем передать результат, который будет использоваться во многих местах, что означает, что мы делаем вычислительно дорогостоящие вещи только один раз:
let finishTheJob = doTheHardStuff(10)
finishTheJob(20)
finishTheJob(30)
Мы можем получить абстракции аналогичным образом.
Функция карри - это функция нескольких аргументов, переписанная так, что она принимает первый аргумент и возвращает функцию, которая принимает второй аргумент и так далее. Это позволяет функциям нескольких аргументов частично применять некоторые из своих начальных аргументов.
map
использовать функцию f
над списком списков, xss
вы можете это сделать map (map f) xss
.
Вот игрушечный пример на Python:
>>> from functools import partial as curry
>>> # Original function taking three parameters:
>>> def display_quote(who, subject, quote):
print who, 'said regarding', subject + ':'
print '"' + quote + '"'
>>> display_quote("hoohoo", "functional languages",
"I like Erlang, not sure yet about Haskell.")
hoohoo said regarding functional languages:
"I like Erlang, not sure yet about Haskell."
>>> # Let's curry the function to get another that always quotes Alex...
>>> am_quote = curry(display_quote, "Alex Martelli")
>>> am_quote("currying", "As usual, wikipedia has a nice summary...")
Alex Martelli said regarding currying:
"As usual, wikipedia has a nice summary..."
(Просто используйте конкатенацию через +, чтобы не отвлекаться на не-Python программистов.)
Редактирование добавить:
См. Http://docs.python.org/library/functools.html?highlight=partial#functools.partial , где также показано различие между частичным объектом и функцией в том, как Python реализует это.
Curry переводит функцию из вызываемой f(a, b, c)
в вызываемую, как f(a)(b)(c)
.
В противном случае каррирование происходит, когда вы разбиваете функцию, которая принимает несколько аргументов, на ряд функций, которые принимают часть аргументов.
В буквальном смысле, карри является преобразованием функций: из одного способа вызова в другой. В JavaScript мы обычно делаем оболочку, чтобы сохранить исходную функцию.
Карринг не вызывает функцию. Это просто трансформирует его.
Давайте создадим функцию карри, которая выполняет каррирование для функций с двумя аргументами. Другими словами, curry(f)
для двух аргументов f(a, b)
переводится вf(a)(b)
function curry(f) { // curry(f) does the currying transform
return function(a) {
return function(b) {
return f(a, b);
};
};
}
// usage
function sum(a, b) {
return a + b;
}
let carriedSum = curry(sum);
alert( carriedSum(1)(2) ); // 3
Как видите, реализация представляет собой серию оболочек.
curry(func)
является обертка function(a)
.sum(1)
, аргумент сохраняется в лексической среде и возвращается новая оболочка function(b)
.sum(1)(2)
наконец, вызывается function(b)
обеспечение 2, и он передает вызов исходной сумме с несколькими аргументами.Если вы понимаете, partial
что вы на полпути. Идея partial
состоит в том, чтобы предварительно применить аргументы к функции и вернуть новую функцию, которая хочет только оставшиеся аргументы. Когда вызывается эта новая функция, она включает в себя предварительно загруженные аргументы вместе с любыми аргументами, которые были ей переданы.
В Clojure +
есть функция, но чтобы все было ясно:
(defn add [a b] (+ a b))
Вы можете знать, что inc
функция просто добавляет 1 к любому числу, которое она передала.
(inc 7) # => 8
Давайте создадим это сами, используя partial
:
(def inc (partial add 1))
Здесь мы возвращаем другую функцию, у которой 1 загружен в первый аргумент add
. Так как add
принимает два аргумента, новая inc
функция хочет только b
аргумент, а не 2 аргумента, как раньше, поскольку 1 уже был применен частично . Таким образом partial
, это инструмент для создания новых функций со стандартными значениями. Вот почему в функциональном языке функции часто упорядочивают аргументы от общего к конкретному. Это облегчает повторное использование таких функций для создания других функций.
Теперь представьте, был ли язык достаточно умен, чтобы понять, что для этого add
нужны два аргумента. Когда мы передали ему один аргумент вместо того, чтобы прекратить, что если функция частично применила аргумент, мы передали его от нашего имени, понимая, что мы, вероятно, намеревались предоставить другой аргумент позже? Мы могли бы определить inc
без явного использования partial
.
(def inc (add 1)) #partial is implied
Так ведут себя некоторые языки. Это исключительно полезно, когда кто-то хочет объединить функции в более крупные преобразования. Это привело бы к преобразователям.
Я нашел эту статью и статью, на которую она ссылается, полезной, чтобы лучше понять карри: http://blogs.msdn.com/wesdyer/archive/2007/01/29/curring-and-partial-function-application.aspx
Как уже упоминалось, это просто способ иметь функцию с одним параметром.
Это полезно, поскольку вам не нужно предполагать, сколько параметров будет передано, поэтому вам не нужны функции с двумя параметрами, тремя параметрами и четырьмя параметрами.
Как и все остальные ответы, карри помогает создавать частично прикладные функции. Javascript не предоставляет встроенную поддержку автоматического карри. Поэтому приведенные выше примеры могут не помочь в практическом кодировании. В LifeScript есть отличный пример (который компилируется в js) http://livescript.net/
times = (x, y) --> x * y
times 2, 3 #=> 6 (normal use works as expected)
double = times 2
double 5 #=> 10
В приведенном выше примере, когда вы указали меньше аргументов, liveScript генерирует для вас новую каррированную функцию (double)
Карри может упростить ваш код. Это одна из главных причин использовать это. Карринг - это процесс преобразования функции, которая принимает n аргументов, в n функций, которые принимают только один аргумент.
Принцип состоит в том, чтобы передать аргументы переданной функции, используя свойство closure (closure), чтобы сохранить их в другой функции и обработать ее как возвращаемое значение, и эти функции образуют цепочку, а окончательные аргументы передаются для завершения операция.
Преимущество этого состоит в том, что он может упростить обработку параметров, работая с одним параметром за раз, что также может улучшить гибкость и удобочитаемость программы. Это также делает программу более управляемой. Кроме того, разделение кода на более мелкие части сделает его более удобным для повторного использования.
Например:
function curryMinus(x)
{
return function(y)
{
return x - y;
}
}
var minus5 = curryMinus(1);
minus5(3);
minus5(5);
Я также могу сделать ...
var minus7 = curryMinus(7);
minus7(3);
minus7(5);
Это очень удобно для создания сложного кода, обработки несинхронизированных методов и т. Д.
Функция карри применяется к нескольким спискам аргументов, а не только к одному.
Вот обычная функция без каррирования, которая добавляет два параметра Int: x и y:
scala> def plainOldSum(x: Int, y: Int) = x + y
plainOldSum: (x: Int,y: Int)Int
scala> plainOldSum(1, 2)
res4: Int = 3
Вот аналогичная функция, которая карри. Вместо одного списка из двух параметров Int вы применяете эту функцию к двум спискам по одному параметру Int каждый:
scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Intscala> second(2)
res6: Int = 3
scala> curriedSum(1)(2)
res5: Int = 3
Здесь происходит то, что когда вы вызываете curriedSum
, вы фактически получаете два традиционных вызова функций друг за другом. Первый вызов функции принимает один именованный параметр Int x
и возвращает значение функции для второй функции. Эта вторая функция принимает параметр Int
y
.
Вот названная функция first
, которая по духу выполняет то, что curriedSum
будет делать первый традиционный вызов функции :
scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int
Применение 1 к первой функции - другими словами, вызов первой функции и передача 1 - дает вторую функцию:
scala> val second = first(1)
second: (Int) => Int = <function1>
Применение 2 ко второй функции дает результат:
scala> second(2)
res6: Int = 3
Примером каррирования может служить то, что, имея функции, на данный момент вам известен только один из параметров:
Например:
func aFunction(str: String) {
let callback = callback(str) // signature now is `NSData -> ()`
performAsyncRequest(callback)
}
func callback(str: String, data: NSData) {
// Callback code
}
func performAsyncRequest(callback: NSData -> ()) {
// Async code that will call callback with NSData as parameter
}
Здесь, поскольку вы не знаете второй параметр для обратного вызова при его отправке, performAsyncRequest(_:)
вам придется создать еще одну лямбду / замыкание, чтобы отправить его в функцию.
func callback
возвращение себя? Это называется @ callback(str)
итак let callback = callback(str)
, обратный вызов - это просто возвращаемое значениеfunc callback
func callback(_:data:)
принимает два параметра, здесь я даю только один, String
поэтому он ожидает следующего ( NSData
), поэтому теперь let callback
другая функция ожидает передачи данных
Вот пример универсальной и самой короткой версии для функции карри при n нет. парам.
const add = a => b => b ? add(a + b) : a;
const add = a => b => b ? add(a + b) : a;
console.log(add(1)(2)(3)(4)());
Здесь вы можете найти простое объяснение реализации карри в C #. В комментариях я попытался показать, как карри может быть полезным:
public static class FuncExtensions {
public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
{
return x1 => x2 => func(x1, x2);
}
}
//Usage
var add = new Func<int, int, int>((x, y) => x + y).Curry();
var func = add(1);
//Obtaining the next parameter here, calling later the func with next parameter.
//Or you can prepare some base calculations at the previous step and then
//use the result of those calculations when calling the func multiple times
//with different input parameters.
int result = func(1);
Карринг является одной из функций высшего порядка в Java Script.
Карринг - это функция многих аргументов, которая переписывается так, что она принимает первый аргумент и возвращает функцию, которая, в свою очередь, использует оставшиеся аргументы и возвращает значение.
Смущенный?
Давайте посмотрим на пример,
function add(a,b)
{
return a+b;
}
add(5,6);
Это похоже на следующую функцию карри,
function add(a)
{
return function(b){
return a+b;
}
}
var curryAdd = add(5);
curryAdd(6);
Так что же означает этот код?
Теперь прочитайте определение снова,
Curry - это функция многих аргументов, которая переписывается так, что она принимает первый аргумент и возвращает функцию, которая в свою очередь использует оставшиеся аргументы и возвращает значение.
Все еще в замешательстве? Позвольте мне объяснить в глубине!
Когда вы вызываете эту функцию,
var curryAdd = add(5);
Он вернет вам такую функцию,
curryAdd=function(y){return 5+y;}
Итак, это называется функциями высшего порядка. Это означает, что вызов одной функции по очереди возвращает другую функцию - это точное определение функции высшего порядка. Это самое большое преимущество для легенды, Java Script. Так что возвращайся к карри,
Эта строка передаст второй аргумент функции curryAdd.
curryAdd(6);
что в свою очередь приводит к
curryAdd=function(6){return 5+6;}
// Which results in 11
Надеюсь, вы понимаете использование карри здесь. Итак, перейдя к преимуществам,
Зачем карри?
Это использует возможность повторного использования кода. Меньше кода, меньше ошибок. Вы можете спросить, как меньше кода?
Я могу доказать это с помощью скрипта ECMA 6 новых функций стрелка функции.
Да! ECMA 6, предоставьте нам замечательную функцию под названием функции стрелки,
function add(a)
{
return function(b){
return a+b;
}
}
С помощью функции со стрелкой мы можем написать вышеуказанную функцию следующим образом:
x=>y=>x+y
Круто верно?
Итак, меньше кода и меньше ошибок!
С помощью этой функции высшего порядка можно легко разработать код без ошибок.
Я бросаю вам вызов!
Надеюсь, вы поняли, что карри. Пожалуйста, не стесняйтесь комментировать здесь, если вам нужны какие-либо разъяснения.
Спасибо хорошего дня!
Есть пример «Карринг в ReasonML».
let run = () => {
Js.log("Curryed function: ");
let sum = (x, y) => x + y;
Printf.printf("sum(2, 3) : %d\n", sum(2, 3));
let per2 = sum(2);
Printf.printf("per2(3) : %d\n", per2(3));
};
curry
иuncurry
функциями Хаскелла. Здесь важно то, что эти изоморфизмы фиксированы заранее и поэтому «встроены» в язык.