Що особливого в каррі або частковому застосуванні?


9

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

Візьмемо цей приклад Groovy як приклад:

def mul = { a, b -> a * b }
def tripler1 = mul.curry(3)
def tripler2 = { mul(3, it) }

Я не розумію, в чому різниця між tripler1і tripler2. Хіба вони не однакові? 'Currying' підтримується чистими або частковими функціональними мовами, такими як Groovy, Scala, Haskell і т. Д. Але я можу зробити те ж саме (лівий каррі, правий каррі, n-каррі або частковий додаток), просто створивши інший названий або анонімний функція або закриття, яке пересилатиме параметри до вихідної функції (як tripler2) у більшості мов (навіть C.)

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

Будь ласка, просвіти мене.

EDIT: Ви, хлопці, говорите, що часткове застосування / каррінг просто ефективніше, ніж створення / виклик іншої функції, яка пересилає параметри за замовчуванням до початкової функції?


1
Чи може хтось створити теги "каррі" чи "каррі"?
Vigneshwaran

Як ви куріть в C?
Джорджіо

це, мабуть, справді більше про програмістів з
questions/152868/…

1
@Vigneshwaran: AFAIK не потрібно створювати іншу функцію мовою, що підтримує currying. Наприклад, у Haskell f x y = x + yозначає, що fце функція, яка приймає один int параметр. Результат f x( fзастосований до x) - це функція, яка приймає один int параметр. Результат f x y(або (f x) y, наприклад, f xзастосований до y) - це вираз, який не приймає вхідних параметрів і оцінюється шляхом зменшення x + y.
Джорджіо

1
Ви можете домогтися тих самих речей, але кількість зусиль, які ви проходите з C, набагато болючіше і не настільки ефективно, як на мові, як haskell, де це поведінка за замовчуванням
Даніель Гратцер,

Відповіді:


8

Заготівля - це перетворення / представлення функції, яка бере n входів у n функцій, кожен з яких займає 1 вхід. Часткове застосування стосується фіксації деяких входів до функції.

Мотивація часткового застосування полягає насамперед у тому, що це спрощує запис бібліотек функцій вищого порядку. Наприклад алгоритми в C ++ STL все в значній мірі приймати предикати або одинарні функції, bind1st дозволяє користувачеві бібліотеки зачепити в НЕ одинарних функцій зі значенням пов'язаного. При цьому бібліотечному письменнику не потрібно надавати перевантажені функції для всіх алгоритмів, які приймають одинарні функції для забезпечення бінарних версій

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


це curryingщо - то конкретне для Groovy або непридатними за мовами?
амфібій

@foampile це щось, що можна застосувати на різних мовах, але за іронією поваги каррі насправді не робить це програмісти.stackexchange.com/
jk.

@jk. Ви хочете сказати, що використання / часткове застосування є більш ефективним, ніж створення та виклик іншої функції?
Vigneshwaran

2
@Vigneshwaran - це не обов'язково більш ефективно, але це, безумовно, більш ефективно з точки зору часу програміста. Також зауважте, що хоча currying підтримується багатьма функціональними мовами, але, як правило, не підтримується OO або процедурними мовами. (Або принаймні, не самою мовою.)
Стівен C

6

Але я можу зробити те ж саме (ліво-каррі, право-каррі, n-каррі або часткове застосування), просто створивши іншу названу або анонімну функцію або закриття, яке перенаправить параметри до вихідної функції (наприклад, триплер2) більшості мов ( навіть С.)

І оптимізатор погляне на це і негайно перейде до чогось, що він може зрозуміти. Крірінг - це приємний маленький трюк для кінцевого користувача, але має набагато кращі переваги з точки зору мовного дизайну. Це дійсно приємно обробляти всі методи як одинарні, A -> Bде Bможе бути інший метод.

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


6

Як @jk. на що натякає, currying може допомогти зробити код більш загальним.

Наприклад, припустимо, у вас були ці три функції (в Haskell):

> let q a b = (2 + a) * b

> let r g = g 3

> let f a b = b (a 1)

Тут функція fприймає дві функції як аргументи, переходить 1до першої функції та передає результат першого виклику другій функції.

Якби ми зателефонували fза допомогою qта rв якості аргументів, це було б ефективно:

> r (q 1)

куди qбуло б застосовано 1та поверне іншу функцію (як qце висловлено); ця повернута функція буде передана rяк її аргумент, якому слід надати аргумент 3. Результатом цього було б значення 9.

Скажімо, у нас були ще дві функції:

> let s a = 3 * a

> let t a = 4 + a

ми можемо також передати їх fі отримати значення 7або 15залежно від того, чи були наші аргументи s tчи t s. Оскільки ці функції обидва повертають значення, а не функцію, часткове застосування не відбудеться в f s tабо f t s.

Якби ми написали fз qі rна увазі , що ми могли б використовувати лямбда (анонімні функції) замість часткового застосування, наприклад:

> let f' a b = b (\x -> a 1 x)

але це обмежило б спільність f'. fможна викликати аргументами qта rабо sі t, але f'можна викликати лише за допомогою qі r- f' s tі f' t sобидва призводять до помилки.

БІЛЬШЕ

Якщо б f'дзвонили з q'/ r'парами, де q'взяли більше двох аргументів, q'то все-таки буде частково застосовано f'.

Крім того, ви можете загорнутись qзовні, fа не зсередини, але це залишить вас з неприємною вкладеною лямбда:

f (\x -> (\y -> q x y)) r

що по суті те, що криве qбуло в першу чергу!


Ти відкрив мені очі. Ваша відповідь дала мені зрозуміти, наскільки curried / частково застосовані функції відрізняються від створення нової функції, яка передає аргументи на вихідну функцію. 1. Проходження навколо закріплених / paed функцій (наприклад, f (q.curry (2)) є більш акуратним, ніж створення окремих функцій без необхідності лише для тимчасового використання (на функціональних мовах, таких як groovy)
Vigneshwaran

2. У своєму запитанні я сказав: "Я можу зробити те саме в C." Так, але в нефункціональних мовах, де ви не можете передавати функції як дані, створюючи окрему функцію, яка пересилає параметри до початкових, не має всіх переваг
currying

Я помітив, що Groovy не підтримує тип узагальнення, який підтримує Haskell. Я повинен був написати , 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до роботи. Ви повинні передати всі параметри або чітко сказати, що ви прогріваєтеся. :(
Vigneshwaran

2
@Vigneshwaran: Так, можна з упевненістю сказати, що Haskell і curry дуже добре йдуть разом . ;] Зауважте, що в Haskell функції за замовчуванням виводяться (у правильному визначенні), а пробіл вказує на застосування функції, тому f x yозначає, що багато мов писали б f(x)(y), а не f(x, y). Можливо, ваш код буде працювати в Groovy, якщо ви пишете qтак, що він очікує, що він буде викликаний як q(1)(2)?
CA McCann

1
@Vigneshwaran Рада, що можу допомогти! Я відчуваю ваш біль через те, що вам потрібно чітко сказати, що ви робите часткове застосування. У Clojure, що я маю робити (partial f a b ...)- звикши до Haskell, мені дуже не вистачає належного currying при програмуванні іншими мовами (хоча я останнім часом працював у F #, який, на щастя, підтримує це).
Павло

3

Є два ключові моменти щодо часткового застосування. Перший - синтаксичний / зручний - деякі означення стають легшими та коротшими для читання та запису, як згадував @jk. (Ознайомтеся з програмою Pointfree, щоб отримати докладнішу інформацію про те, наскільки це приголомшливо!)

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

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

приймати єдиний аргумент; через асоціативність конструктора типу функції ->вищезазначене еквівалентно:

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

що є функцією, яка приймає aі повертає функцію [a] -> [a].

Це дозволяє нам писати такі функції, як:

($) :: (a -> b) -> a -> b

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

f :: (t, t1) -> t -> t1 -> (t2 -> t3 -> (t, t1)) -> t2 -> t3 -> [(t, t1)]
f q r s t u v = q : (r, s) : [t u v]

f' :: () -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)]
f' = f $ ((), 'a')  -- <== works fine

Гаразд, так це було надуманим прикладом. Але більш корисний стосується класу типу додаткового типу , який включає цей метод:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

Як бачите, тип ідентичний подібному, $якщо ви забираєте Applicative fбіт, і насправді цей клас описує функціональну програму в контексті. Отже замість звичайного застосування функції:

ghci> map (+3) [1..5]  
[4,5,6,7,8]

Ми можемо застосовувати функції в додатковому контексті; наприклад, у контексті "Можливо", в якому щось може бути присутнім або відсутнім:

ghci> Just map <*> Just (+3) <*> Just [1..5]
Just [4,5,6,7,8]

ghci> Just map <*> Nothing <*> Just [1..5]
Nothing

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

fA' :: Maybe (() -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)])
fA' = Just f <*> Just ((), 'a')

Наскільки мені відомо, клас типу Applicative у загальному вигляді був би неможливим без певної концепції часткового застосування. (Будь-яким експертам з програмування там - будь ласка, виправте мене, якщо я помиляюся!) Звичайно, якщо у вашій мові бракує часткового застосування, ви можете створити її в якійсь формі, але ... це просто не те саме, чи не так? ? :)


1
Applicativeбез curry або часткового застосування не використовує fzip :: (f a, f b) -> f (a, b). Мовою, що має функції вищого порядку, це дозволяє підняти каррінг та часткове застосування в контексті функтора і еквівалентно (<*>). Без функцій вищого порядку у вас не буде, fmapтому вся справа була б марною.
CA McCann

@CAMcCann дякую за відгук! Я знав, що у мене над головою ця відповідь. Так це те, що я сказав неправильно?

1
Дуже правильно, звичайно. Розщеплення волосків за визначеннями "загальна форма", "можливо" та "концепція часткового застосування" не змінить простого факту, що чарівний f <$> x <*> yідіоматичний стиль працює легко, тому що завивка і часткове застосування легко працюють. Іншими словами, те, що приємно , важливіше, ніж те, що тут можливо .
CA McCann

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

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