Я каждый день читаю статьи по функциональному программированию и стараюсь как можно больше применять некоторые практики. Но я не понимаю, что уникально в карри или частичном применении.
Возьмем этот Groovy-код в качестве примера:
def mul = { a, b -> a * b }
def tripler1 = mul.curry(3)
def tripler2 = { mul(3, it) }
Я не понимаю, в чем разница между tripler1
и tripler2
. Разве они не одинаковы? 'Curry' поддерживается в чистых или частично функциональных языках, таких как Groovy, Scala, Haskell и т. Д. Но я могу сделать то же самое (левое карри, правое карри, n-карри или частичное приложение), просто создав другое именованное или анонимное функция или замыкание, которое будет перенаправлять параметры в исходную функцию (например tripler2
) на большинстве языков (даже на C.)
Я что-то здесь упускаю? Есть места, где я могу использовать карри и частичную аппликацию в своем приложении Grails, но я не решаюсь сделать это, потому что спрашиваю себя: "Как это отличается?"
Пожалуйста, просветите меня.
РЕДАКТИРОВАТЬ: Вы, ребята, говорите, что частичное применение / каррирование просто более эффективно, чем создание / вызов другой функции, которая перенаправляет параметры по умолчанию в исходную функцию?
источник
f x y = x + y
означает, чтоf
это функция, которая принимает один параметр int. Результатомf x
(f
применяется кx
) является функция, которая принимает один параметр типа int. Результатf x y
(или(f x) y
, т.f x
Е. Примененный кy
) является выражением, которое не принимает входных параметров и оценивается путем сокращенияx + y
.Ответы:
Карринг - это превращение / представление функции, которая принимает n входов в n функций, каждая из которых принимает 1 вход. Частичное применение - это исправление некоторых входных данных для функции.
Мотивация для частичного применения заключается, прежде всего, в том, что это облегчает написание библиотек функций более высокого порядка. Например, все алгоритмы в C ++ STL в основном принимают предикаты или унарные функции, bind1st позволяет пользователю библиотеки подключать не унарные функции с ограниченным значением. Поэтому создателю библиотеки не нужно предоставлять перегруженные функции для всех алгоритмов, которые принимают унарные функции для предоставления двоичных версий
Само по себе карри полезно, потому что оно дает вам частичное применение везде, где вы хотите, бесплатно, т.е. вам больше не нужна функция, подобная
bind1st
частичному применению.источник
currying
что - то конкретное для Groovy или неприменимыми по языкам?И оптимизатор посмотрит на это и быстро перейдет к тому, что сможет понять. Карринг - это приятный маленький трюк для конечного пользователя, но он имеет гораздо больше преимуществ с точки зрения языкового дизайна. Это действительно приятно обрабатывать все методы как унарные,
A -> B
гдеB
может быть другим способом.Это упрощает методы, которые вы должны написать для обработки функций более высокого порядка. Ваш статический анализ и оптимизация на языке имеет только один путь для работы, который ведет себя известным образом. Привязка параметров просто выпадает из проекта, а не требует, чтобы обручи выполняли это обычное поведение.
источник
Как @jk. Выражение карри может помочь сделать код более общим.
Например, предположим, что у вас были эти три функции (в Haskell):
Функция
f
здесь принимает две функции в качестве аргументов, передает1
первую функцию и передает результат первого вызова второй функции.Если бы мы вызывали
f
с использованиемq
иr
в качестве аргументов, это было бы эффективно:где
q
будет применяться1
и возвращать другую функцию (какq
карри); эта возвращаемая функция затем будет передана вr
качестве аргумента для аргумента3
. Результатом этого будет значение9
.Теперь, скажем, у нас было две другие функции:
мы могли бы также передать их
f
и получить значение7
или15
, в зависимости от того, были ли наши аргументыs t
илиt s
. Так как эти функции возвращают значение , а не функции, не частичное применение не будет иметь место вf s t
илиf t s
.Если бы мы написали
f
сq
иr
в виду , что мы могли бы использовать лямбда (анонимные функции) вместо частичного применения, например:но это ограничило бы общность
f'
.f
можно вызывать с аргументамиq
иr
илиs
иt
, ноf'
можно вызывать только сq
иr
-f' s t
иf' t s
оба приводят к ошибке.БОЛЬШЕ
Если бы он
f'
вызывался с паройq'
/,r'
гдеq'
аргумент принимает более двух аргументов,q'
он все равно будет частично применен вf'
.В качестве альтернативы, вы можете обернуть
q
снаружи,f
а не внутри, но это оставит вас с неприятной вложенной лямбдой:что по сути то, что карри
q
был на первом месте!источник
def f = { a, b -> b a.curry(1) }
чтобы заставитьf q, r
работатьdef f = { a, b -> b a(1) }
или илиdef f = { a, b -> b a.curry(1)() }
дляf s, t
работы. Вы должны передать все параметры или явно сказать, что вы карри. :(f x y
означает, что многие языки будут писатьf(x)(y)
, а неf(x, y)
. Возможно, ваш код будет работать в Groovy, если вы напишитеq
так, что он будет называться какq(1)(2)
?(partial f a b ...)
- будучи привыкшим к Haskell, я очень скучаю по правильному карри при программировании на других языках (хотя я недавно работал на F #, который, к счастью, поддерживает это).Есть два ключевых момента о частичном применении. Первый - синтаксический / удобный - некоторые определения становятся проще и короче для чтения и записи, как упоминалось в @jk. (Проверьте Pointfree программирования для получения дополнительной информации о том, как это здорово!)
Второй, как упоминал @telastyn, касается модели функций и не просто удобен. В версии на Haskell, из которой я получу свои примеры, потому что я не знаком с другими языками с частичным применением, все функции принимают один аргумент. Да, даже такие функции, как:
принять один аргумент; из-за ассоциативности конструктора функционального типа
->
вышеприведенное эквивалентно:которая является функцией, которая принимает
a
и возвращает функцию[a] -> [a]
.Это позволяет нам писать такие функции, как:
который может применять любую функцию к аргументу соответствующего типа. Даже сумасшедшие, такие как:
Ладно, это был надуманный пример. Но более полезный включает класс типов Applicative , который включает этот метод:
Как видите, тип идентичен аналогичному,
$
если вы убираетеApplicative f
бит, и фактически этот класс описывает применение функции в контексте. Итак, вместо обычного приложения функции:Мы можем применять функции в аппликативном контексте; например, в контексте Maybe, в котором что-то может присутствовать или отсутствовать:
Теперь действительно прохладная часть является то , что Прикладное класс типа не упоминает ничего о функциях более одного аргумента - тем не менее, он может иметь дело с ними, даже функциями 6 аргументов , такими как
f
:Насколько я знаю, класс типов Applicative в его общей форме был бы невозможен без какой-либо концепции частичного применения. (Любому специалисту по программированию - пожалуйста, поправьте меня, если я ошибаюсь!) Конечно, если вашему языку не хватает частичного применения, вы можете встроить его в какой-то форме, но ... это просто не то же самое, не так ли? ? :)
источник
Applicative
без карри или частичного применения будет использоватьfzip :: (f a, f b) -> f (a, b)
. В языке с функциями более высокого порядка это позволяет поднять каррирование и частичное применение в контекст функтора и эквивалентно(<*>)
. Без функций высшего порядка у вас не будет,fmap
поэтому все это будет бесполезно.f <$> x <*> y
идиоматический стиль работает легко, потому что карри и частичное применение работают легко. Другими словами, то, что приятно, важнее, чем то, что здесь возможно .