Groovy называет частичное приложение «карри»?

15

В Groovy есть концепция, которая называется «карри». Вот пример из их вики:

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

def halver = divide.rcurry(2)

assert halver(8) == 4

Мое понимание того, что здесь происходит, заключается в том, что правый аргумент divideсвязывается со значением 2. Это похоже на форму частичного применения.

Термин карринг обычно используется для обозначения преобразования функции, которая принимает серию аргументов, в функцию, которая принимает только один аргумент и возвращает другую функцию. Например, вот тип curryфункции в Haskell:

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

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

Я неправильно понял, что происходит в отличном примере, или это просто неправильное частичное применение? Или это действительно делает и то и другое: то есть преобразовать divideв карри функцию и затем частично применить 2к этой новой функции.

Ричард Уорбертон
источник
1
для тех, кто не говорит на haskell msmvps.com/blogs/jon_skeet/archive/2012/01/30/…
jk.

Ответы:

14

Реализация Groovy на curryсамом деле не карри в любой момент, даже за кулисами. Это практически идентично частичному применению.

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

Когда укупорочное вызывается, в конечном итоге это вызывает в 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метод так, чтобы при отсутствии аргументов он выполнял реальное каррирование, и не рекомендуется вызывать метод с аргументами в пользу новой partialфункции. Это может показаться немного странным , но это максимизирует обратную совместимость - поскольку нет причин использовать частичное приложение с нулевыми аргументами - при этом избегая (IMHO) более уродливой ситуации наличия новой функции с другим именем для правильного каррирования, в то время как функция фактически named 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 Строго говоря, «цель» каррирования - преобразовать функцию с множеством аргументов во вложенную цепочку функций с одним аргументом в каждом. Я думаю, что вы просите практическую причину, по которой вы хотите использовать карри, что немного сложнее оправдать в Groovy, чем, например, в LISP или Haskell. Дело в том, что вы, вероятно, хотите использовать большую часть времени, это частичное применение, а не карри.
Джордан Грей
4
+1 заand you die a little inside
Томас Эдинг
1
Ух ты, я понимаю, что карри намного лучше после прочтения твоего ответа: +1 и спасибо! Конкретно по вопросу, мне нравится, что вы сказали «смешанное карри с частичным применением».
ГленПетерсон
3

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

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

его карри форма будет

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

однако карри groovy вернет что-то эквивалентное (при условии, что вызывается с 1 аргументом x)

fgroovy :: (b,c) -> d 

который будет вызывать f со значением фиксированного х

т. е. хотя groovy-карри может возвращать функции с N-1 аргументами, у функций с карри только один аргумент, поэтому groovy не может быть карри с карри

JK.
источник
2

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

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

Существующая функциональность останется в той или иной форме, и обратная совместимость будет приниматься во внимание при вызове того, как назвать новые методы и т. Д., Поэтому я не могу сказать на данном этапе, каким будет окончательное именование новых / старых методов. быть. Наверное, компромисс по именованию, но посмотрим.

Для большинства программистов ОО различие между двумя терминами (карринг и частичное применение), возможно, в значительной степени академическое; однако, как только вы привыкнете к ним (и тот, кто будет поддерживать ваш код, будет обучен чтению этого стиля кодирования), тогда бессмысленное программирование или программирование в неявном стиле (которое поддерживает «реальное» каррирование) позволяет более компактно выражать определенные виды алгоритмов. а в некоторых случаях более элегантно. Очевидно, здесь есть некоторая «красота в глазах смотрящего», но способность поддерживать оба стиля соответствует природе Groovy (OO / FP, static / dynamic, классы / сценарии и т. Д.).

Пол Кинг
источник
1

Учитывая это определение, найденное в IBM:

Термин карри взят из Хаскелла Карри, математика, который разработал концепцию частичных функций. Карринг относится к получению нескольких аргументов в функцию, которая принимает много аргументов, в результате чего получается новая функция, которая принимает оставшиеся аргументы и возвращает результат.

halverваша новая (карри) функция (или замыкание), которая теперь принимает только один параметр. Звонок halver(10)приведет к 5.

Для этого он преобразует функцию с n аргументами в функцию с n-1 аргументами. То же самое говорит ваш пример на Haskell, что делает карри.

matcauthon
источник
4
Определение IBM неверно. То, что они определяют как каррирование, на самом деле является частичным применением функции, которое связывает (исправляет) аргументы функции, чтобы создать функцию с меньшей арностью. Карринг преобразует функцию, которая принимает несколько аргументов, в цепочку функций, каждая из которых принимает один аргумент.
Джордан Грей
1
В википедии определено то же, что и в IBM: в математике и информатике карринг - это метод преобразования функции, которая принимает несколько аргументов (или n-кортеж аргументов) таким образом, что ее можно назвать цепочка функций, каждая с одним аргументом (частичное применение). Groovy превращает функцию (с двумя аргументами) с rcurryфункцией (которая принимает один аргумент) в функцию (с теперь только одним аргументом). Я приковал функцию curry аргументом к моей базовой функции, чтобы получить свою результирующую функцию.
Matcauthon
3
Нет, определение в Википедии другое - частичное применение - это когда вы вызываете каррированную функцию, а не когда вы ее определяете, как это делает groovy
jk.
6
@jk правильно. Прочитайте объяснение в Википедии еще раз, и вы увидите, что возвращаемая цепочка функций с одним аргументом, а не одна функция с n - 1аргументами. Смотрите пример в конце моего ответа; также см. далее в статье для получения дополнительной информации о проводимых различий. en.wikipedia.org/wiki/…
Джордан Грэй,
4
Это очень важно, поверь мне. Опять же, код в конце моего ответа демонстрирует, как будет работать правильная реализация; для этого не потребуются никакие аргументы. Текущая реализация должна действительно называться, напримерpartial .
Джордан Грей