У чому перевага каррі?


154

Щойно я дізнався про каррі, і, хоча я думаю, що розумію цю концепцію, я не бачу великої переваги в її використанні.

Як тривіальний приклад я використовую функцію, яка додає два значення (записані в ML). Версія без завивки буде

fun add(x, y) = x + y

і буде називатися як

add(3, 5)

в той час як викришена версія є

fun add x y = x + y 
(* short for val add = fn x => fn y=> x + y *)

і буде називатися як

add 3 5

Мені здається, це просто синтаксичний цукор, який видаляє один набір дужок із визначення та виклику функції. Я бачив currying, перерахований як одна з важливих особливостей функціональних мов, і на даний момент я трохи не підданий йому. Концепція створення ланцюга функцій, що споживають кожен окремий параметр, замість функції, яка приймає кортеж, здається досить складною для використання для простої зміни синтаксису.

Чи є дещо простіший синтаксис єдиною мотивацією для завивки, чи я пропускаю деякі інші переваги, які не очевидні в моєму дуже простому прикладі? Крірі - це лише синтаксичний цукор?


54
Сам по собі каррі по суті марний, але використання всіх функцій за замовчуванням робить багато інших функцій набагато приємніше використовувати. Важко оцінити це, поки ви фактично не використовували функціональну мову деякий час.
CA McCann

4
Щось, про що згадували мимоволі в коментарі до відповіді ДжоельЕтертона, але я думав, що я прямо зазначу, це те, що (принаймні в Haskell) ви можете частково застосувати не тільки функції, але й конструктори типів - це може бути цілком зручно; це може бути над чим подумати.
Павло

Усі навели приклади Haskell. Можливо, здається, що приготування корисної корисності лише в Haskell.
Manoj R

@ManojR Всі не наводили прикладів у Haskell.
phwd

1
Питання породило досить цікаву дискусію щодо Reddit .
Янніс

Відповіді:


126

Завдяки фігурним функціям ви отримуєте легше повторне використання більш абстрактних функцій, оскільки ви можете спеціалізуватися. Скажімо, у вас є функція додавання

add x y = x + y

і що ви хочете додати 2 до кожного учасника списку. У Haskell ви зробите це:

map (add 2) [1, 2, 3] -- gives [3, 4, 5]
-- actually one could just do: map (2+) [1, 2, 3], but that may be Haskell specific

Тут синтаксис легший, ніж якби вам довелося створити функцію add2

add2 y = add 2 y
map add2 [1, 2, 3]

або якщо вам довелося зробити анонімну лямбда-функцію:

map (\y -> 2 + y) [1, 2, 3]

Це також дозволяє абстрагуватися від різних реалізацій. Скажімо, у вас було дві функції пошуку. Один зі списку пар ключів / значень і ключ до значення, а інший - з карти від клавіш до значень і ключ до значення, як це:

lookup1 :: [(Key, Value)] -> Key -> Value -- or perhaps it should be Maybe Value
lookup2 :: Map Key Value -> Key -> Value

Тоді ви можете зробити функцію, яка приймає функцію пошуку від ключа до значення. Ви можете передавати їй будь-яку з наведених вище функцій пошуку, частково застосованих або зі списком, або з картою відповідно:

myFunc :: (Key -> Value) -> .....

На закінчення: currying - це добре, оскільки дозволяє спеціалізуватися / частково застосовувати функції за допомогою легкого синтаксису, а потім передавати ці частково застосовані функції навколо функції вищого порядку, таких як mapабо filter. Функції вищого порядку (які приймають функції як параметри або дають їх як результати) - це хліб та масло функціонального програмування, а функція каррінгу та частково застосовані функції дозволяють використовувати функції вищого порядку набагато ефективніше та стисліше.


31
Варто зазначити, що через це порядок аргументів, який використовується для функцій Haskell, часто базується на тому, наскільки ймовірне часткове застосування, що, в свою чергу, змушує описані вище переваги (ha, ha) у більшості ситуацій. Таким чином, вигадка за замовчуванням виходить навіть кориснішою, ніж це видно з конкретних прикладів, таких як наведені тут.
CA McCann

ват. "Один зі списку пар ключів / значень і ключ до значення, а інший з карти від клавіш до значень і ключ до значення"
Mateen Ulhaq

@MateenUlhaq Це продовження попереднього речення, де я гадаю, що ми хочемо отримати значення на основі ключа, і у нас є два способи зробити це. У реченні перераховано ці два способи. По-перше, вам надається перелік пар ключів / значень і ключ, для якого ми хочемо знайти значення, а в іншому випадку нам надається відповідна карта, і знову ключ. Це може допомогти подивитися на код відразу після речення.
Борис

53

Практична відповідь полягає в тому, що currying значно спрощує створення анонімних функцій. Навіть з синтаксисом мінімального лямбда - це щось виграшне; порівняти:

map (add 1) [1..10]
map (\ x -> add 1 x) [1..10]

Якщо у вас є потворний лямбда-синтаксис, це ще гірше. (Я дивлюся на вас, JavaScript, схему та Python.)

Це стає все кориснішим, коли ви використовуєте все більше і більше функцій вищого порядку. Хоча я використовую більше функцій вищого порядку в Haskell, ніж в інших мовах, я виявив, що фактично використовую синтаксис лямбда менше, оскільки щось на зразок двох третин часу, лямбда буде просто частково застосованою функцією. (І багато іншого разу я витягую це в іменовану функцію.)

Більш принципово, не завжди очевидно, яка версія функції є "канонічною". Наприклад, візьміть map. Тип mapможна записати двома способами:

map :: (a -> b) -> [a] -> [b]
map :: (a -> b) -> ([a] -> [b])

Який із них є "правильним"? Це насправді важко сказати. На практиці більшість мов використовують першу - карта бере функцію та список та повертає список. Однак принципово те, що насправді робить карта, - це відображення звичайних функцій для переліку функцій - вона бере функцію і повертає функцію. Якщо карта є кривою, вам не доведеться відповідати на це питання: це робить і те , і дуже елегантно.

Це стає особливо важливим після узагальнення mapдо інших типів, ніж список.

Крім того, каррі дійсно не дуже складний. Це насправді трохи спрощення щодо моделі, якою користується більшість мов: вам не потрібне поняття про функції декількох аргументів, вкладених у вашу мову. Це також відображає більш глибоке обчислення лямбда.

Звичайно, мови в стилі ML не мають поняття про численні аргументи у вишуканому вигляді або в незрозумілому вигляді. f(a, b, c)Синтаксис фактично відповідає проходженню в кортежі (a, b, c)в f, так до fсих пір приймає тільки аргумент. Це насправді дуже корисна відмінність, яку я хотів би мати в інших мовах, тому що це дуже природно написати щось на зразок:

map f [(1,2,3), (4,5,6), (7, 8, 9)]

Ви не могли легко зробити це з мовами, які мають ідею про декілька аргументів, викладених прямо!


1
"Мови в стилі ML не мають поняття про численні аргументи у вишуканому вигляді або в необробленому вигляді": в цьому відношенні чи стиль ML Haskell ML?
Джорджіо

1
@Giorgio: Так.
Тихон Єлвіс

1
Цікаво. Я знаю деякі Haskell, і я зараз навчаюсь SML, тому цікаво побачити відмінності та схожість між двома мовами.
Джорджіо

Чудова відповідь, і якщо ви все ще не впевнені, просто подумайте про трубопроводи Unix, схожі на лямбда-потоки
Шрідар Шарнобат

"Практична" відповідь не дуже важлива, оскільки багатослів'я зазвичай уникають частковим застосуванням , а не прокатуванням. І я б тут заперечував, що синтаксис лямбда-абстракції (незважаючи на декларацію типу) є гіршим, ніж (принаймні) у схемі, оскільки для правильного розбору потрібні більш вбудовані спеціальні синтаксичні правила, які розкривають специфікацію мови без будь-якого посилення про смислові властивості.
FrankHB

24

Каррінг може бути корисним, якщо у вас є функція, яку ви обминаєте як об'єкт першого класу, і ви не отримуєте всіх параметрів, необхідних для її оцінки в одному місці в коді. Ви можете просто застосувати один або декілька параметрів, коли їх отримаєте, і передайте результат іншому фрагменту коду, який має більше параметрів, і закінчіть його там.

Код для цього буде простішим, ніж якщо потрібно спочатку зібрати всі параметри.

Також існує можливість повторного використання коду, оскільки функції, що приймають один параметр (інша викрита функція), не повинні збігатися так само конкретно з усіма параметрами.


14

Основна мотивація (принаймні спочатку) для каррі була не практичною, а теоретичною. Зокрема, currying дозволяє ефективно отримувати функції багато аргументів, не визначаючи для них семантику чи визначаючи семантику для продуктів. Це призводить до спрощення мови з такою ж виразністю, як і інша, більш складна мова, і так бажано.


2
Хоча мотивація тут теоретична, я вважаю, що простота майже завжди є практичною перевагою. Не турбуючись про багатоаргументальні функції полегшує моє життя, коли я програмуюсь так само, як це було б, якби я працював із семантикою.
Тихон Єлвіс

2
@TikhonJelvis Однак, коли ви програмуєте, currying надає вам інших проблем, як, наприклад, компілятор не сприймає той факт, що ви передали занадто мало аргументів функції або навіть отримує повідомлення про помилку в цьому випадку; коли ви не використовуєте каррі, помилка набагато очевидніша.
Alex R

У мене ніколи не було таких проблем: GHC, принаймні, дуже хороший у цьому плані. Компілятор завжди вловлює подібну проблему, а також має хороші повідомлення про помилки.
Тихон Єлвіс

1
Я не можу погодитися, що повідомлення про помилки кваліфікуються як хороші. Так, але вони ще не хороші. Він також вловлює такий тип проблеми, якщо це призводить до помилки типу, тобто якщо ви пізніше намагаєтеся використовувати результат як щось інше, ніж функцію (або ви вводили примітки, але покладаючись на це для помилок, що читаються, є свої проблеми ); повідомлення про помилку відокремлено від фактичного місцезнаходження.
Alex R

14

(Я наведу приклади в Haskell.)

  1. Під час використання функціональних мов дуже зручно, що ви можете частково застосувати функцію. Як у Haskell's (== x)- це функція, яка повертається, Trueякщо її аргумент дорівнює заданому терміну x:

    mem :: Eq a => a -> [a] -> Bool
    mem x lst = any (== x) lst
    

    без каррінга у нас буде трохи менш читабельний код:

    mem x lst = any (\y -> y == x) lst
    
  2. Це пов'язано з програмуванням Tacit (див. Також стиль Pointfree на вікі Haskell). Цей стиль орієнтований не на значення, представлені змінними, а на складання функцій та те, як інформація протікає по ланцюгу функцій. Ми можемо перетворити наш приклад у форму, яка зовсім не використовує змінні:

    mem = any . (==)
    

    Тут ми розглядаємо ==як функцію від aдо a -> Boolі anyяк функцію від a -> Boolдо [a] -> Bool. Просто склавши їх, ми отримуємо результат. Це все завдяки каррі.

  3. Зворотний, не криючий, також корисний у деяких ситуаціях. Наприклад, скажімо, що ми хочемо розділити список на дві частини - елементи, менші за 10, та інші, а потім об'єднати ці два списки. Розщеплення списку здійснюється за допомогою (тут ми також використовуємо витримку ). Результат - тип . Замість того, щоб витягувати результат у своїй першій та другій частині та комбінувати їх за допомогою , ми можемо це зробити безпосередньо, розкручуючи якpartition (< 10)<([Int],[Int])++++

    uncurry (++) . partition (< 10)
    

Дійсно, (uncurry (++) . partition (< 10)) [4,12,11,1]оцінює до [4,1,12,11].

Є також важливі теоретичні переваги:

  1. Заготівля має важливе значення для мов, у яких відсутні типи даних і мають лише функції, наприклад, лямбда-числення . Хоча ці мови не корисні для практичного використання, вони дуже важливі з теоретичної точки зору.
  2. Це пов'язано з істотною властивістю функціональних мов - функції є об'єктом першого класу. Як ми бачили, перехід від (a, b) -> cдо a -> (b -> c)означає, що результат останньої функції має тип b -> c. Іншими словами, результат є функцією.
  3. (Un) каррі тісно пов'язаний із декартовими закритими категоріями , що є категоричним способом перегляду набраних лямбда-обчислень.

Біт "набагато менш читабельного коду" не повинен бути mem x lst = any (\y -> y == x) lst? (З зворотним нахилом).
стусміт

Так, дякую, що вказав на це, я виправлю це.
Петро Пудлак

9

Каррінг - це не просто синтаксичний цукор!

Розглянемо підписи типів add1(uncurried) та add2(curried):

add1 : (int * int) -> int
add2 : int -> (int -> int)

(В обох випадках дужки в підписі типу необов’язкові, але я включив їх для наочності.)

add1це функція , яка приймає 2-кортеж intі intі повертає int. add2це функція, яка приймає intта повертає іншу функцію, яка, у свою чергу, приймає intта повертає int.

Істотна різниця між ними стає більш помітною, коли ми чітко визначаємо програму функції. Давайте визначимо функцію (не криво), яка застосовує перший аргумент до свого другого аргументу:

apply(f, b) = f b

Тепер ми можемо бачити різницю між add1і add2чіткіше. add1телефонує з 2-ма кордоном:

apply(add1, (3, 5))

але add2викликається з, int а його повернене значення викликається іншимint :

apply(apply(add2, 3), 5)

EDIT: Основна перевага currying полягає в тому, що ви отримуєте часткове застосування безкоштовно. Скажімо, ви хотіли функцію типу int -> int(скажімо, mapїй над списком), яка додала 5 до свого параметра. Ви можете написати addFiveToParam x = x+5, або ви могли б зробити еквівалент вбудованою лямбда, але ви також могли набагато легше (особливо у випадках менш тривіальних, ніж цей) add2 5!


3
Я розумію, що за моїм прикладом існує велика різниця, але результат здається простою синтаксичною зміною.
Mad Scientist

5
Каррінг не дуже глибоке поняття. Йдеться про спрощення основної моделі (див. Обчислення лямбда) або на мовах, які так чи інакше мають кортежі, насправді йдеться про синтаксичну зручність часткового застосування. Не варто недооцінювати важливість синтаксичної зручності.
Пік

9

Крірі - це лише синтаксичний цукор, але ви трохи не розумієте, що робить цукор, я думаю. Беручи свій приклад,

fun add x y = x + y

насправді синтаксичний цукор для

fun add x = fn y => x + y

Тобто, (add x) повертає функцію, яка бере аргумент y, і додає x до y.

fun addTuple (x, y) = x + y

Це функція, яка бере кортеж і додає його елементи. Ці дві функції насправді досить різні; вони беруть різні аргументи.

Якщо ви хочете додати 2 до всіх номерів у списку:

(* add 2 to all numbers using the uncurried function *)
map (fn x => addTuple (x, 2)) [1,2,3]
(* using the curried function *)
map (add 2) [1,2,3]

Результат був би [3,4,5].

Якщо ви хотіли підсумовувати кожен кортеж у списку, з іншого боку, функція addTuple ідеально підходить.

(* Sum each tuple using the uncurried function *)
map addTuple [(10,2), (10,3), (10,4)]    
(* sum each tuple using curried function *)
map (fn (a,b) => add a b) [(10,2), (10,3), (10,4)]

Результат був би [12,13,14].

Загорнуті функції чудові там, де корисне часткове застосування - наприклад, карта, складання, додаток, фільтр. Розглянемо цю функцію, яка повертає найбільше додатне число у наданому списку, або 0, якщо немає позитивних чисел:

- val highestPositive = foldr Int.max 0;   
val highestPositive = fn : int list -> int 

1
Я зрозумів, що крива функція має інший тип підпису, і що це насправді функція, яка повертає іншу функцію. Я частково додав частину заяви.
Mad Scientist

9

Інша річ, яку я ще не бачив, - це те, що каррі дозволяє (обмежувати) абстракцію над сукупністю.

Розглянемо ці функції, що входять до бібліотеки Haskell

(.) :: (b -> c) -> (a -> b) -> a -> c
either :: (a -> c) -> (b -> c) -> Either a b -> c
flip :: (a -> b -> c) -> b -> a -> c
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c

У кожному випадку змінна типу cможе бути типом функції, так що ці функції працюють над деяким префіксом списку параметрів їх аргументу. Без curry, вам або потрібна спеціальна мовна функція, щоб абстрагуватися над функцією arity, або мати багато різних версій цих функцій, спеціалізованих для різних архітектур.


6

Моє обмежене розуміння таке:

1) Часткове застосування функції

Застосування часткової функції - це процес повернення функції, яка бере меншу кількість аргументів. Якщо ви надасте 2 з 3 аргументів, він поверне функцію, яка приймає 3-2 = 1 аргумент. Якщо ви надаєте 1 з 3 аргументів, він поверне функцію, яка бере 3-1 = 2 аргументи. Якби ви хотіли, ви могли б навіть частково застосувати 3 з 3 аргументів, і це поверне функцію, яка не бере аргументу.

Отже, дана наступна функція:

f(x,y,z) = x + y + z;

Прив’язуючи 1 до x та частково застосовуючи це до вищевказаної функції, f(x,y,z)ви отримаєте:

f(1,y,z) = f'(y,z);

Де: f'(y,z) = 1 + y + z;

Тепер, якщо ви прив’язуєте y до 2 і z до 3, і частково застосовуєте, f'(y,z)ви отримаєте:

f'(2,3) = f''();

Де f''() = 1 + 2 + 3:;

Тепер у будь-який момент можна вибрати оцінку f, f'або f''. Тож я можу зробити:

print(f''()) // and it would return 6;

або

print(f'(1,1)) // and it would return 3;

2) Каррінг

З іншого боку, каррінг - це процес розбиття функції на вкладений ланцюг з однієї функції аргументу. Ніколи не можна навести більше одного аргументу, це один або нуль.

Отже, дана однакова функція:

f(x,y,z) = x + y + z;

Якби ви його зависли, ви отримаєте ланцюжок з 3-х функцій:

f'(x) -> f''(y) -> f'''(z)

Де:

f'(x) = x + f''(y);

f''(y) = y + f'''(z);

f'''(z) = z;

Тепер, якщо ви телефонуєте f'(x)з x = 1:

f'(1) = 1 + f''(y);

Вам повертається нова функція:

g(y) = 1 + f''(y);

Якщо ви телефонуєте за g(y)допомогою y = 2:

g(2) = 1 + 2 + f'''(z);

Вам повертається нова функція:

h(z) = 1 + 2 + f'''(z);

Нарешті, якщо ви телефонуєте h(z)з z = 3:

h(3) = 1 + 2 + 3;

Вас повернули 6.

3) закриття

Нарешті, закриття - це процес захоплення функції та даних разом як єдине ціле. Закриття функції може зайняти 0 до нескінченної кількості аргументів, але вона також усвідомлює не передані їй дані.

Знову ж таки, задавши ту саму функцію:

f(x,y,z) = x + y + z;

Замість цього можна написати завершення:

f(x) = x + f'(y, z);

Де:

f'(y,z) = x + y + z;

f'закрито на x. Значення, яке f'може прочитати значення x, яке знаходиться всередині f.

Так що якщо ви повинні були зателефонувати fз x = 1:

f(1) = 1 + f'(y, z);

Ви отримаєте закриття:

closureOfF(y, z) =
                   var x = 1;
                   f'(y, z);

Тепер , якщо ви подзвонили closureOfFз y = 2і z = 3:

closureOfF(2, 3) = 
                   var x = 1;
                   x + 2 + 3;

Який би повернувся 6

Висновок

Дегустація, часткове застосування та закриття дещо подібні тим, що вони розкладають функцію на більшу частину.

Currying розкладає функцію з декількох аргументів на вкладені функції одиничних аргументів, які повертають функції окремих аргументів. Немає сенсу викривляти функцію одного або менш аргументів, оскільки це не має сенсу.

Часткове застосування розкладає функцію з декількох аргументів на функцію менших аргументів, чиї відсутні аргументи були замінені на надане значення.

Закриття розбиває функцію на функцію та набір даних, де змінні всередині функції, які не передавались, можуть заглянути всередині набору даних, щоб знайти значення, яке пов'язує із собою, коли запитують оцінити.

Що все бентежить у тому, що кожен з них може використовуватись для реалізації підмножини інших. Таким чином, вони по суті є деталями реалізації. Всі вони надають аналогічну цінність у тому, що вам не потрібно збирати всі значення заздалегідь і тим, що ви можете повторно використовувати частину функції, оскільки ви розклали її на стримані одиниці.

Розкриття інформації

Я аж ніяк не знавець цієї теми, я лише нещодавно почав дізнаватися про них, і тому я даю своє сучасне розуміння, але це може мати помилки, які я запрошую вас вказати, і я виправлю як / якщо Я відкриваю будь-яку.


1
Отже, відповідь: curry не має переваги?
припинення

1
@ceving Наскільки я знаю, це правильно. На практиці каррі та часткове застосування дадуть вам однакові переваги. Вибір того, який спосіб реалізувати в мові, робиться з міркувань впровадження, можливо, простіше реалізувати, а інший із певною мовою.
Дідьє А.

5

Заготівля (часткове застосування) дозволяє створювати нову функцію з існуючої функції, фіксуючи деякі параметри. Це особливий випадок лексичного закриття, коли анонімна функція є лише тривіальною обгорткою, яка передає деякі захоплені аргументи іншій функції. Ми також можемо це зробити, використовуючи загальний синтаксис для складання лексичних закриттів, але часткове застосування забезпечує спрощений синтаксичний цукор.

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

Замість того (lambda (x) (+ 3 x)), що дає нам функцію, яка додає 3 до свого аргументу, ви можете написати щось на кшталт (op + 3), і тому додати 3 до кожного елемента деякого списку було б тоді, (mapcar (op + 3) some-list)а не (mapcar (lambda (x) (+ 3 x)) some-list). Цей opмакрос зробить вас функцією, яка бере деякі аргументи x y z ...та викликає (+ a x y z ...).

У багатьох чисто функціональних мовах часткове застосування вбудоване в синтаксис, щоб не було opоператора. Щоб запустити часткове застосування, ви просто викликаєте функцію з меншою кількістю аргументів, ніж потрібно. Замість того, щоб створювати "insufficient number of arguments"помилку, результат є функцією решти аргументів.


«Каррінг ... дозволяє створити нову функцію ... фіксуючи деякі параметри» - немає, функція типу a -> b -> cне має параметр s ( у множині), він має тільки один параметр, c. При виклику він повертає функцію типу a -> b.
Макс Хайбер

4

Для функції

fun add(x, y) = x + y

Він має форму f': 'a * 'b -> 'c

Оцінити один зробимо

add(3, 5)
val it = 8 : int

Для викривленої функції

fun add x y = x + y

Оцінити один зробимо

add 3
val it = fn : int -> int

Де це часткове обчислення, зокрема (3 + у), яке потім можна завершити обчисленням

it 5
val it = 8 : int

Додайте у другому випадку форму f: 'a -> 'b -> 'c

Те, що тут робиться, - це перетворення функції, яка бере дві угоди в одну, яка бере лише один повертаючий результат. Часткова оцінка

Навіщо це потрібно?

Скажіть xпро RHS - це не просто звичайний int, а натомість складний обчислення, який потребує певного часу, щоб збільшити, для збільшення, ради, дві секунди.

x = twoSecondsComputation(z)

Отож функція тепер виглядає так

fun add (z:int) (y:int) : int =
    let
        val x = twoSecondsComputation(z)
    in
        x + y
    end;

Типу add : int * int -> int

Тепер ми хочемо обчислити цю функцію для діапазону чисел, давайте відобразимо її

val result1 = map (fn x => add (20, x)) [3, 5, 7];

Для вищезазначеного результат twoSecondsComputationоцінюється кожен раз. Це означає, що для цього обчислення потрібно 6 секунд.

Використовуючи комбінацію постановки та каррі, можна цього уникнути.

fun add (z:int) : int -> int =
    let
        val x = twoSecondsComputation(z)
    in
        (fn y => x + y)
    end;

З вичавленої форми add : int -> int -> int

Тепер це можна зробити,

val add' = add 20;
val result2 = map add' [3, 5, 7, 11, 13];

В twoSecondsComputationтільки потрібно оцінювати один раз. Щоб збільшити масштаб, замініть дві секунди на 15 хвилин або будь-яку годину, а потім отримайте карту на 100 цифр.

Короткий зміст : Карі є чудовим при використанні інших методів для функцій вищого рівня як інструменту часткової оцінки. Її призначення справді не можна продемонструвати самому.


3

Крірінг дозволяє гнучкий склад функції.

Я склав функцію "каррі". У цьому контексті мені байдуже, який саме лісоруб я отримую або звідки він походить. Мені байдуже, що це за акція чи звідки вона виходить. Все, що мені важливо - це обробка мого вкладу.

var builder = curry(function(input, logger, action) {
     logger.log("Starting action");
     try {
         action(input);
         logger.log("Success!");
     }
     catch (err) {
         logger.logerror("Boo we failed..", err);
     }
});
var x = "My input.";
goGatherArgs(builder)(x); // Supplies action first, then logger somewhere.

Змінна builder - це функція, яка повертає функцію, яка повертає функцію, яка приймає мій внесок, який виконує мою роботу. Це простий корисний приклад, а не предмет на очах.


2

Заготівля - це перевага, коли у вас немає всіх аргументів для функції. Якщо ви повністю оцінюєте функцію, значущої різниці немає.

За допомогою каррі можна уникати згадування ще не потрібних параметрів. Він більш стислий і не вимагає пошуку імені параметра, який не стикається з іншою змінною за обсягом (що є моєю улюбленою перевагою).

Наприклад, використовуючи функції, які приймають функції в якості аргументів, ви часто опиняєтесь у ситуаціях, коли вам потрібні функції типу "додати 3 до введення" або "порівняти вхід зі змінною v". За допомогою curry, ці функції легко записуються: add 3і (== v). Без curry, ви повинні використовувати лямбда-вирази: x => add 3 xі x => x == v. Лямбда-вирази вдвічі довші і займають невелику кількість напруженої роботи, пов’язаної з вибором імені, крім того, xякщо він вже є xв області застосування.

Побічна перевага мов, заснованих на currying, полягає в тому, що, записуючи загальний код для функцій, ви не закінчуєте сотнями варіантів на основі кількості параметрів. Наприклад, у C # методу «каррі» знадобляться варіанти для Func <R>, Func <A, R>, Func <A1, A2, R>, Func <A1, A2, A3, R> тощо навіки. У Haskell еквівалент Func <A1, A2, R> більше нагадує Func <Tuple <A1, A2>, R> або Func <A1, Func <A2, R >> (і Func <R> більше нагадує Func <Unit, R>), тому всі варіанти відповідають одному випадку Func <A, R>.


2

Первинне міркування, про яке я можу придумати (і я жодним чином не знаю спеціалістів з цього питання), починає демонструвати свої переваги, коли функції переходять від тривіального до нетривіального. У всіх тривіальних випадках із більшості понять подібного характеру ви не знайдете реальної користі. Однак більшість функціональних мов сильно використовують стек в операціях обробки. Розгляньте PostScript або Lisp як приклади цього. Використовуючи використання каррі, функції можна укладати ефективніше, і ця вигода стає очевидною, оскільки операції стають все менш тривіальними. У докладному порядку команда та аргументи можуть бути кинуті на стек в порядку та вискакуватися за необхідності, щоб вони запускалися у належному порядку.


1
Як саме вимагає створити набагато більше фреймів стека, щоб зробити речі більш ефективними?
Мейсон Уілер

1
@MasonWheeler: Я б не знав, тому що я сказав, що я не фахівець з функціональних мов або конкретно каррінг. Я позначив цю вікі спільноти саме через це.
Джоель Етертон

4
@MasonWheeler У вас є точкова написання цієї відповіді, але дозвольте мені зафіксувати і сказати, що кількість створених фреймів стека багато в чому залежить від реалізації. Наприклад, у безшпиндельній машині G без проблем (STG; спосіб, яким GHC реалізує Haskell) затримує фактичну оцінку, поки вона не накопичить усі (або принаймні стільки, скільки знає, що потрібно) аргументів. Я не можу пригадати, чи це робиться для всіх функцій чи лише для конструкторів, але я думаю, що це повинно бути можливим для більшості функцій. (Знову ж таки, поняття "стек фрейми" насправді не стосується STG.)

1

Заготівля кардинально залежить (остаточно рівномірно) від здатності повертати функцію.

Розглянемо цей (надуманий) псевдо код.

var f = (m, x, b) => ... повернути щось ...

Зазначимо, що виклик f з меншими трьома аргументами повертає функцію.

var g = f (0, 1); // це повертає функцію, пов'язану з 0 і 1 (m і x), яка приймає ще один аргумент (b).

var y = g (42); // викликати g з відсутнім третім аргументом, використовуючи 0 і 1 для m і x

Те, що ви можете частково застосувати аргументи і повернути повторно використану функцію (пов'язану з тими аргументами, які ви поставили), є цілком корисним (і DRY).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.