В чем разница между карринг и частичное применение?
Я довольно часто вижу в Интернете различные жалобы на то, что другие народы примеры карринга не каррируют, а на самом деле просто частичное применение.
Я не нашел достойного объяснения того, что такое частичное применение, или как оно отличается от карринга. По-видимому, существует общая путаница, когда эквивалентные примеры описываются как каррирование в некоторых местах и частичное применение в других.
может ли кто-нибудь дать мне определение обоих термины, и детали как они отличаются?
13 ответов:
каррирование-это преобразование одной функции n аргументы в n функции с одним аргументом каждого. Учитывая следующую функцию:
function f(x,y,z) { z(x(y));}
когда Карри, приобретает следующий вид:
function f(x) { lambda(y) { lambda(z) { z(x(y)); } } }
для того,чтобы получить полное применение f(x,y, z), вам нужно сделать это:
f(x)(y)(z);
многие функциональные языки позволяют писать
f x y z
. Если вы только позвонитеf x y
или f (x) (y) затем вы получаете частично примененный функция-возвращаемое значение является замыканиемlambda(z){z(x(y))}
с переданными значениями x и y вf(x,y)
.один из способов использования частичного приложения-определить функции как частичные приложения обобщенных функций, например раза:
function fold(combineFunction, accumalator, list) {/* ... */} function sum = curry(fold)(lambda(accum,e){e+accum}))(0); function length = curry(fold)(lambda(accum,_){1+accum})(empty-list); function reverse = curry(fold)(lambda(accum,e){concat(e,accum)})(empty-list); /* ... */ @list = [1, 2, 3, 4] sum(list) //returns 10 @f = fold(lambda(accum,e){e+accum}) //f = lambda(accumaltor,list) {/*...*/} f(0,list) //returns 10 @g = f(0) //same as sum g(list) //returns 10
самый простой способ, чтобы увидеть, как они отличаются, чтобы рассмотреть реальный пример. Предположим, что у нас есть функция
Add
который принимает 2 числа в качестве входных и возвращает число в качестве выходных, напримерAdd(7, 5)
возвращает12
. В этом случае:
частичное применение функции
Add
со значением7
даст нам новую функцию в качестве результата. Сама эта функция принимает 1 число в качестве входного и выводит число. Как такие:Partial(Add, 7); // returns a function f2 as output // f2 takes 1 number as input and returns a number as output
так что мы можем сделать это:
f2 = Partial(Add, 7); f2(5); // returns 12; // f2(7)(5) is just a syntactic shortcut
карринг функции
Add
даст нам новую функцию в качестве результата. Эта функция сама принимает 1 число в качестве входа и выхода и все же еще одна новая функция. Затем эта третья функция принимает 1 число в качестве входного и возвращает число в качестве выходного. Как таковой:Curry(Add); // returns a function f2 as output // f2 takes 1 number as input and returns a function f3 as output // i.e. f2(number) = f3 // f3 takes 1 number as input and returns a number as output // i.e. f3(number) = number
так что мы можем сделать это:
f2 = Curry(Add); f3 = f2(7); f3(5); // returns 12
другими словами, "каррирование" и "частичное применение" - это две совершенно разные функции. каррирование занимает ровно 1 вход, в то время как частичное применение занимает 2 (или более) входов.
несмотря на то, что они оба возвращают функцию в качестве вывода, возвращаемые функции имеют совершенно разные формы, как показано выше.
Примечание: это было взято из F # Основы отличная вступительная статья для разработчиков .NET, попадающих в функциональное программирование.
каррирование означает разбиение функции со многими аргументами на ряд функции, которые принимают один аргумент и в конечном итоге же результат, что и исходная функция. Карринг, пожалуй, самый сложная тема для разработчиков, не знакомых с функциональным программированием, особенно потому, что она часто путают с частичным применением. Вы можете увидеть оба на работе в этом примере:
let multiply x y = x * y let double = multiply 2 let ten = double 5
сразу же, вы должны увидеть поведение, которое отличается от большинства императивные языки. Второй оператор создает новую функцию вызывается double путем передачи одного аргумента функции, которая принимает два. Результатом является функция, которая принимает один аргумент типа int и возвращает тот же вывод, как если бы вы вызвали умножение с x равным 2 и y равный этому аргументу. В терминах поведение, это то же самое, что и это код:
let double2 z = multiply 2 z
часто люди ошибочно говорят, что умножение-это карри для образования двойника. Но это лишь отчасти верно. Функция умножения является Карри, но это происходит, когда он определен, потому что функции в F# каррируются по умолчанию. Когда двойная функция создана, она более точна к скажем, что функция умножения применяется частично.
функция умножения на самом деле представляет собой ряд из двух функций. Этот первый функция принимает один аргумент типа int и возвращающую другую функцию, эффективная привязка x к определенному значению. Эта функция также принимает аргумент int, который можно рассматривать как значение для привязки к y. After вызывая эту вторую функцию, x и y оба связаны, поэтому результат произведение x и y, как определено в теле двойника.
чтобы создать double, первая функция в цепочке умножения функции оцениваются для частичного применения умножения. В результате функция получает название дважды. Когда double оценивается, он использует его аргумент вместе с частично примененным значением для создания результат.
интересный вопрос. После небольшого поиска,"частичное применение функции-это не карринг" дал лучшее объяснение, которое я нашел. Я не могу сказать, что практические разница особенно очевидна для меня, но тогда я не эксперт FP...
еще одна полезная страница (которую, признаюсь, я еще не полностью прочитал) - "каррирование и частичное применение с закрытиями Java".
похоже, что это широко запутано пара терминов, заметьте.
Я ответил на это в другом потоке https://stackoverflow.com/a/12846865/1685865 . Короче говоря, применение частичной функции заключается в фиксации некоторых аргументов данной многомерной функции для получения другой функции с меньшим количеством аргументов, в то время как карринг-это превращение функции из N аргументов в унарную функцию, которая возвращает унарную функцию...[Пример Карринга показан в конце этого поста.]
карринг в основном представляет теоретический интерес: можно выразить вычисления, используя только унарные функции (т. е.
разницу между Карри и частичным приложением лучше всего проиллюстрировать на следующем примере JavaScript:
function f(x, y, z) { return x + y + z; } var partial = f.bind(null, 1); 6 === partial(2, 3);
частичное применение приводит к функции меньшей арности; в приведенном выше примере
f
имеет арность 3 в то время какpartial
имеет только арность 2. Что еще более важно, частично применяемая функция будет возвращает результат сразу после вызова, а не другая функция вниз по цепочке карринга. Так что если вы что-то видите какpartial(2)(3)
, это не частичное применение на практике.читайте далее:
для меня частичное приложение должно создать новую функцию, где используемые аргументы полностью интегрированы в результирующую функцию.
большинство функциональных языков реализуют каррирование, возвращая закрытие: не оценивайте под лямбда при частичном применении. Итак, чтобы частичное применение было интересным, нам нужно сделать разницу между каррированием и частичным применением и рассмотреть частичное применение как каррирование плюс оценка под лямбда.
Я мог бы очень ошибаться здесь, поскольку у меня нет сильного фона в теоретической математике или функциональном программировании, но из моего краткого набега на FP кажется, что карринг имеет тенденцию превращать функцию N аргументов в N функций одного аргумента, тогда как частичное применение [на практике] лучше работает с вариативными функциями с неопределенным числом аргументов. Я знаю, что некоторые примеры в предыдущих ответах бросают вызов этому объяснению, но это помогло мне больше всего отделить концепция. Рассмотрим этот пример (написанный в CoffeeScript для краткости, мои извинения, если он смущает дальше, но, пожалуйста, попросите разъяснений, если это необходимо):
# partial application partial_apply = (func) -> args = [].slice.call arguments, 1 -> func.apply null, args.concat [].slice.call arguments sum_variadic = -> [].reduce.call arguments, (acc, num) -> acc + num add_to_7_and_5 = partial_apply sum_variadic, 7, 5 add_to_7_and_5 10 # returns 22 add_to_7_and_5 10, 11, 12 # returns 45 # currying curry = (func) -> num_args = func.length helper = (prev) -> -> args = prev.concat [].slice.call arguments return if args.length < num_args then helper args else func.apply null, args helper [] sum_of_three = (x, y, z) -> x + y + z curried_sum_of_three = curry sum_of_three curried_sum_of_three 4 # returns a function expecting more arguments curried_sum_of_three(4)(5) # still returns a function expecting more arguments curried_sum_of_three(4)(5)(6) # returns 15 curried_sum_of_three 4, 5, 6 # returns 15
это явно надуманный пример, но обратите внимание, что частичное применение функции, которая принимает любое количество аргументов, позволяет нам выполнить функцию, но с некоторыми предварительными данными. Каррирование функции аналогично, но позволяет нам выполнять функцию N-параметра по частям до тех пор, но только до тех пор, пока все N параметры учитываются.
опять же, это мой взгляд на вещи, которые я читал. Если кто-то не согласен, я был бы признателен за комментарий о том, почему, а не немедленное понижение. Кроме того, если CoffeeScript трудно читать, пожалуйста, посетите coffeescript.org, нажмите кнопку "try coffeescript" и вставьте в мой код, чтобы увидеть скомпилированную версию, которая может (надеюсь) иметь больше смысла. Спасибо!
у меня был этот вопрос много во время обучения и с тех пор его задавали много раз. Самый простой способ, которым я могу описать разницу, заключается в том, что оба они одинаковы :) позвольте мне объяснить...есть очевидные различия.
как частичное применение, так и карринг включают в себя предоставление аргументов функции, возможно, не все сразу. Довольно канонический пример-добавление двух чисел. В псевдокоде (фактически JS без ключевых слов) базовая функция может быть следующее:
add = (x, y) => x + y
если бы я хотел функцию "addOne", я мог бы частично применить ее или карри:
addOneC = curry(add, 1) addOneP = partial(add, 1)
теперь с их помощью понятно:
addOneC(2) #=> 3 addOneP(2) #=> 3
так в чем же разница? Ну, это тонкое, но частичное приложение включает в себя предоставление некоторых аргументов, и возвращаемая функция будет тогда выполнить основную функцию при следующем вызове в то время как карринг будет ждать, пока у него не будет всех аргументов необходимо:
curriedAdd = curry(add) # notice, no args are provided addOne = curriedAdd(1) # returns a function that can be used to provide the last argument addOne(2) #=> returns 3, as we want partialAdd = partial(add) # no args provided, but this still returns a function addOne = partialAdd(1) # oops! can only use a partially applied function once, so now we're trying to add one to an undefined value (no second argument), and we get an error
короче говоря, используйте частичное приложение для предварительного заполнения некоторых значений, зная, что в следующий раз при вызове метода он будет выполняться, оставляя неопределенными все непроверенные аргументы; используйте currying, когда вы хотите постоянно возвращать частично примененную функцию столько раз, сколько необходимо для выполнения сигнатуры функции. Последний надуманный пример:
curriedAdd = curry(add) curriedAdd()()()()()(1)(2) # ugly and dumb, but it works partialAdd = partial(add) partialAdd()()()()()(1)(2) # second invocation of those 7 calls fires it off with undefined parameters
надеюсь, что это помогает!
обновление: некоторые языки или реализации lib позволят вам передайте arity (общее количество аргументов в окончательной оценке) частичной реализации приложения, которая может объединить мои два описания в запутанный беспорядок...но в этот момент эти два метода в значительной степени взаимозаменяемы.
карринг является функцией один аргумент, который принимает функцию
f
и возвращает новую функциюh
:curry(f) = h
частичное применение является функцией два(или более) аргументы, которые принимает функция
f
и один или несколько дополнительных аргументовf
и возвращает новую функциюg
:part(f, 2) = g
путаница возникает потому, что с функцией двух аргументов следующее равенство держит:
partial(f, a) = curry(f)(a)
обе стороны будут давать одну и ту же функцию с одним аргументом.
равенство не верно для более высоких функций arity, потому что в этом случае карринг вернет функцию с одним аргументом, тогда как частичное приложение вернет функцию с несколькими аргументами.
разница также в поведении, в то время как карринг преобразует всю исходную функцию рекурсивно (один раз для каждого аргумента), частичное применение-это всего лишь один шаг замена.
источник: Wikipedia Currying.
есть и другие отличные ответы здесь, но я считаю, что это пример (в моем понимании) в Java может быть полезным для некоторых людей:
public static <A,B,X> Function< B, X > partiallyApply( BiFunction< A, B, X > aBiFunction, A aValue ){ return b -> aBiFunction.apply( aValue, b ); } public static <A,X> Supplier< X > partiallyApply( Function< A, X > aFunction, A aValue ){ return () -> aFunction.apply( aValue ); } public static <A,B,X> Function< A, Function< B, X > > curry( BiFunction< A, B, X > bif ){ return a -> partiallyApply( bif, a ); }
таким образом, currying дает вам функцию с одним аргументом для создания функций, где partial-application создает функцию-оболочку, которая жестко кодирует один или несколько аргументов.
Если вы хотите скопировать и вставить, следующее более шумное, но более дружелюбное для работы, так как типы более мягкие:
public static <A,B,X> Function< ? super B, ? extends X > partiallyApply( final BiFunction< ? super A, ? super B, X > aBiFunction, final A aValue ){ return b -> aBiFunction.apply( aValue, b ); } public static <A,X> Supplier< ? extends X > partiallyApply( final Function< ? super A, X > aFunction, final A aValue ){ return () -> aFunction.apply( aValue ); } public static <A,B,X> Function< ? super A, Function< ? super B, ? extends X > > curry( final BiFunction< ? super A, ? super B, ? extends X > bif ){ return a -> partiallyApply( bif, a ); }
при написании этого, я перепутал каррирование и uncurrying. Это обратные преобразования по функциям. Это действительно не имеет значения, что вы называете, пока вы получаете то, что преобразование и его обратное представление.
Uncurrying не определен очень четко (или, скорее, есть "противоречивые" определения, которые все захватывают дух идеи). По сути, это означает превращение функцию, которая принимает несколько аргументов в функцию, которая принимает один аргумент. Для например,
(+) :: Int -> Int -> Int
Итак, как же превратить это в функцию, которая принимает один аргумент? Ты жульничаешь, конечно!
plus :: (Int, Int) -> Int
обратите внимание, что плюс теперь принимает один аргумент (который состоит из двух вещей). Супер!
какой в этом смысл? Ну, если у вас есть функция, которая принимает два аргумента, и у вас есть пара аргументов, приятно знать, что вы можете применить функцию к аргументам и все равно получить то, что вы ожидаете. И, на самом деле, сантехника для этого уже существует, так что вам не нужно делать такие вещи, как явное сопоставление шаблонов. Все, что вам нужно сделать, это:
(uncurry (+)) (1,2)
так что же такое частичная функция приложения? Это другой способ превратить функцию в двух аргументах в функцию с одним аргументом. Он работает по-другому. Опять же, давайте возьмем (+) в качестве примера. Как мы можем превратить его в функцию, которая принимает int в качестве аргумента? Мы жульничаем!
((+) 0) :: Int -> Int
Это функция, которая добавляет ноль к любому Инт.
((+) 1) :: Int -> Int
добавляет 1 к любому Int. Так далее. В каждом из этих случаев (+) применяется "частично".
простой ответ
Карри: позволяет вызывать функцию, разделяя ее на несколько вызовов, предоставляя один аргумент для каждого вызова.
фрагмент: позволяет вызывать функцию, разделяя ее на несколько вызовов, предоставляя несколько аргументов для каждого вызова.
простые советы
оба позволяют вызывать функцию, предоставляющую меньше аргументов (или, лучше, предоставляя их кумулятивно). На самом деле оба они связывают (при каждом вызове) в конкретное значение для конкретных аргументов функции.
реальную разницу можно увидеть, когда функция имеет более 2 аргументов.
простой e (C) (образец)
(в Javascript)
function process(context, success_callback, error_callback, subject) {...}
зачем всегда передавать аргументы, такие как контекст и обратные вызовы, если они всегда будут одинаковыми? Просто свяжите некоторые значения для функции
processSubject = _.partial(process, my_context, my_success, my_error)
и вызова его на subject1 и foobar С
processSubject('subject1'); processSubject('foobar');
удобно, не так ли?
С карринг вам нужно будет передать один аргумент за раз
curriedProcess = _.curry(process); processWithBoundedContext = curriedProcess(my_context); processWithCallbacks = processWithBoundedContext(my_success)(my_error); // note: these are two sequential calls result1 = processWithCallbacks('subject1'); // same as: process(my_context, my_success, my_error, 'subject1'); result2 = processWithCallbacks('foobar'); // same as: process(my_context, my_success, my_error, 'foobar');
отказ от ответственности
я пропустил все академические / математические объяснения. Потому что я этого не знаю. Может быть, это помогло