Я бачив посилання на викривлені функції в кількох статтях і блогах, але не можу знайти хорошого пояснення (або принаймні одного, яке має сенс!)
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-кортежем), є дещо неелегантною - але, як довів Мойсей Шенфінкель (і, незалежно, Haskell Curry), це не потрібно: всі ви потрібні функції, які беруть один аргумент.
Отже, як ви маєте справу з чимось, що ви, природно, висловлюєте як, скажімо 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.
На закінчення: -
Різниця між застосуванням каррі та частковим застосуванням функції полягає в тому, що: -
Currying приймає функцію і надає нову функцію, що приймає один аргумент, і повертає вказану функцію з її першим аргументом, встановленим на цей аргумент. Це дозволяє нам представляти функції з кількома параметрами у вигляді серії функцій одного аргументу . Приклад: -
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 заданих 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 реалізує це.
Currying - це переклад функції з callable як f(a, b, c)
в callable як f(a)(b)(c)
.
Інакше currying - це коли ви розбиваєте функцію, яка бере кілька аргументів на ряд функцій, які беруть частину аргументів.
Буквально каррінг - це перетворення функцій: з одного способу виклику в інший. У JavaScript ми зазвичай робимо обгортку, щоб зберегти початкову функцію.
Заготівля не викликає функції. Це просто перетворює.
Давайте зробимо функцію curry, яка виконує curry для двоаргументних функцій. Іншими словами, 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/currying-and-partial-function-application.aspx
Як згадували інші, це лише спосіб мати функцію одного параметра.
Це корисно тим, що вам не потрібно вважати, скільки параметрів буде передано, тому вам не потрібні 2 параметри, 3 параметри та 4 параметри функції.
Як і всі інші відповіді, currying допомагає створити частково застосовані функції. Javascript не забезпечує вбудовану підтримку автоматичного висихання. Отже, наведені вище приклади можуть не допомогти в практичному кодуванні. У живописі (який по суті складається з js) є чудовий приклад http://livescript.net/
times = (x, y) --> x * y
times 2, 3 #=> 6 (normal use works as expected)
double = times 2
double 5 #=> 10
У наведеному вище прикладі, коли ви надали менше аргументів, сценарій виконання сценарію створює для вас нову функцію (подвійний)
Каррі може спростити ваш код. Це одна з головних причин використовувати це. Currying - це процес перетворення функції, яка приймає n аргументів у n функцій, які приймають лише один аргумент.
Принцип полягає в тому, щоб передавати аргументи переданої функції, використовуючи властивість закриття (закриття), зберігати їх в іншій функції і трактувати її як повернене значення, і ці функції утворюють ланцюг, і остаточні аргументи передаються для завершення операція.
Перевага цього полягає в тому, що воно може спростити обробку параметрів, обробляючи один параметр одночасно, що також може підвищити гнучкість і читабельність програми. Це також робить програму більш керованою. Також поділ коду на більш дрібні шматочки зробить його зручним для повторного використання.
Наприклад:
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
Прикладом currying може бути, коли функцій ви знаєте лише один з параметрів на даний момент:
Наприклад:
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:)
приймає два параметри, тут я даю лише один, the String
, тому він чекає наступного ( NSData
), ось чому зараз let callback
є ще одна функція, яка чекає передачі даних
Ось приклад загальної та найкоротшої версії для функціонування currying з n no. парам.
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)());
Тут ви можете знайти просте пояснення реалізації currying у 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.
Currying - це функція багатьох аргументів, яка переписана таким чином, що вона бере перший аргумент і повертає функцію, яка в свою чергу використовує решта аргументів і повертає значення.
Плутати?
Давайте подивимось приклад,
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);
То що означає цей код?
Тепер знову прочитайте визначення,
Currying - це функція багатьох аргументів, яка переписується таким чином, що вона бере перший аргумент і повертає функцію, яка в свою чергу використовує решта аргументів і повертає значення.
Все-таки плутати? Дозвольте пояснити глибоко!
Коли ви викликаєте цю функцію,
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
Сподіваємось, ви тут зрозуміли використання каррі. Отже, переходячи до переваг,
Чому каррінг?
Він використовує можливість повторного використання коду. Менше коду, менше помилок. Ви можете запитати, як це менше коду?
Я можу це довести за допомогою сценарію 6 нових функцій стрілки ECMA.
Так! ECMA 6, надайте нам чудову функцію, яку називають стрілковими функціями,
function add(a)
{
return function(b){
return a+b;
}
}
За допомогою функції стрілки ми можемо записати описану вище функцію так,
x=>y=>x+y
Класно, правда?
Отже, менше коду та менше помилок !!
За допомогою цих функцій вищого порядку можна легко розробити код без помилок.
Я кидаю виклик тобі!
Сподіваюся, ви зрозуміли, що таке каррі. Будь ласка, не соромтесь коментувати тут, якщо вам потрібні якісь пояснення.
Спасибі, приємного дня!
Є приклад "Currying in 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
функції Хаскелла. Тут важливо те, що ці ізоморфізми заздалегідь фіксуються, а тому «вбудовуються» в мову.