Чи називає groovy часткове застосування «каррінг»?


15

У Groovy існує концепція, яку він називає «каррінг». Ось приклад з їхньої вікі:

def divide = { a, b -> a / b }

def halver = divide.rcurry(2)

assert halver(8) == 4

Я розумію, що відбувається тут, полягає в тому, що правий аргумент divideприв’язується до значення 2. Це здається формою часткового застосування.

Термін currying зазвичай використовується для означання перетворення функції, яка бере ряд аргументів у функцію, яка бере лише один аргумент і повертає іншу функцію. Наприклад, ось тип curryфункції в Haskell:

curry :: ((a, b) -> c) -> (a -> (b -> c))

Для людей, які не використовували Haskell a, bі cвсі загальні параметри. curryприймає функцію з двома аргументами і повертає функцію, яка приймає aта повертає функцію з bдо c. Я додав додаткову пару дужок до типу, щоб зробити це більш зрозумілим.

Чи я неправильно зрозумів, що відбувається в грубому прикладі, або це просто неправильне часткове застосування? Або насправді це робить і те, і інше: тобто перетворити divideна функцію curried, а потім частково застосувати 2до цієї нової функції.


1
для тих, хто не говорить haskell msmvps.com/blogs/jon_skeet/archive/2012/01/30/…
jk.

Відповіді:


14

Реалізація Groovy curryнасправді не викликає куріння, навіть за кулісами. Він по суті ідентичний частковому застосуванню.

В curry, rcurryі ncurryметоди повертають CurriedClosureоб'єкт , який утримує пов'язані аргументи. Він також має метод getUncurriedArguments(неправильно названий - ви curry функції, а не аргументи), який повертає композицію переданих йому аргументів зв'язаними аргументами.

Коли укупорочное викликається, в кінцевому підсумку це викликає в invokeMethodметодMetaClassImpl , який явно перевіряє , щоб побачити , якщо викликає об'єкт є екземпляром CurriedClosure. Якщо це так, він використовує вищезгадане, getUncurriedArgumentsщоб скласти повний масив аргументів для застосування:

if (objectClass == CurriedClosure.class) {
    // ...
    final Object[] curriedArguments = cc.getUncurriedArguments(arguments);
    // [Ed: Yes, you read that right, curried = uncurried. :) ]
    // ...
    return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments);
}

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

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

Само собою зрозуміло, що результат виклику curryабсолютно відрізняється від фактичного висипу. Якби це справді викривило функцію, ви зможете написати:

def add = { x, y -> x + y }
def addCurried = add.curry()   // should work like { x -> { y -> x + y } }
def add1 = addCurried(1)       // should work like { y -> 1 + y }
assert add1(1) == 2 

… І це спрацювало б, бо addCurriedтреба працювати так { x -> { y -> x + y } }. Натомість це кидає виняток із виконання, і ти трохи помреш всередині.


1
Я думаю, що rcurry і ncurry на функціях з аргументами> 2 демонструють, що це дійсно просто часткове застосування, а не curry
jk.

@jk Насправді це видно на функціях з аргументами == 2, як я зауважую в кінці. :)
Джордан Грей

3
@matcauthon Строго кажучи, "мета" currying полягає в перетворенні функції з багатьма аргументами в вкладений ланцюг функцій з одним аргументом кожен. Я думаю, що те, про що ти просиш, - це практична причина, коли ти хочеш використовувати каррі, що в Groovy трохи жорсткіше виправдати, ніж, наприклад, LISP або Haskell. Справа в тому, що ви, мабуть, хочете використовувати більшу частину часу, це часткове нанесення, а не отримання каррі.
Джордан Грей

4
+1 заand you die a little inside
Томас Едінг

1
Нічого собі, я розумію, що цікавитись набагато краще, прочитавши вашу відповідь: +1 і дякую! Характерне для запитання, мені подобається, що ви сказали, "зв'язаний каррі з частковим застосуванням".
GlenPeterson

3

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

f :: (a,b,c) -> d

його викривлена ​​форма була б

fcurried :: a -> b -> c -> d

проте curry groovy поверне щось еквівалентне (припускаючи, що викликається з 1 аргументом x)

fgroovy :: (b,c) -> d 

який буде викликати f зі значенням фіксованого на x

тобто, хоча curry groryy може повертати функції з аргументами N-1, curry функції належним чином мають лише один аргумент, тому groovy не може бути curry з curry


2

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

http://groovy.markmail.org/thread/c4ycxdzm3ack6xxb

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

Для більшості програмістів OO відмінність між двома термінами (каррінг та часткове застосування), мабуть, значною мірою є академічним; однак, як тільки ви звикли до них (і хто буде підтримувати ваш код, навчений читати цей стиль кодування), тоді безпроблемне або мовчазне програмування стилю (яке "справжня" підтримка currying) дозволяє певні види алгоритмів виражатись більш компактно а в деяких випадках більш елегантно. Очевидно, тут є якась "краса в очах глядача", але можливість підтримувати обидва стилі відповідає природі Гроові (OO / FP, статична / динамічна, класи / сценарії тощо).


1

Враховуючи це визначення, знайдене в IBM:

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

halver- це ваша нова (зрізана) функція (або закриття), яка тепер бере лише один параметр. Виклик halver(10)призведе до 5.

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


4
Визначення IBM невірно. Те, що вони визначають як currying, - це фактично часткове застосування функції, яке пов'язує (виправляє) аргументи функції, щоб зробити функцію з меншою сутністю. Currying перетворює функцію, яка бере декілька аргументів, у ланцюг функцій, кожен з яких бере один аргумент.
Джордан Грей

1
У дефінітоні вікіпедії те саме, що і в IBM: У математиці та інформатиці currying - це техніка перетворення функції, яка приймає кілька аргументів (або n-рядок аргументів) таким чином, що її можна назвати як ланцюг функцій, кожна з яких має один аргумент (часткове застосування). Groovy перетворює функцію (з двома аргументами) з rcurryфункцією (яка бере один аргумент) у функцію (з тепер лише одним аргументом). Я прикував функцію каррі аргументом до своєї базової функції, щоб отримати отриману функцію.
matcauthon

3
Ні, визначення wikipedia відрізняється - часткове застосування - це коли ви викликаєте функцію curried - не тоді, коли ви визначаєте її, що робить groovy
jk.

6
@jk правильний. Прочитайте ще раз пояснення Вікіпедії, і ви побачите, що повертається - це ланцюжок функцій з одним аргументом кожен, а не одна функція з n - 1аргументами. Дивіться приклад наприкінці моєї відповіді; також див. пізніше у статті докладніше про відмінність. en.wikipedia.org/wiki/…
Джордан Грей

4
Це дуже важливо, повірте. Знову код у кінці моєї відповіді демонструє, як би працювала правильна реалізація; для одного не було б ніяких аргументів. Поточна реалізація дійсно повинні бути названі , наприклад partial.
Джордан Грей
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.