Чому я повинен використовувати аплікативні функтори у функціональному програмуванні?


78

Я новачок у Haskell, і я читаю про функтори та аплікативні функтори. Добре, я розумію функтори та те, як я можу ними користуватися, але я не розумію, чому аплікативні функтори корисні та як я можу використовувати їх у Haskell. Чи можете ви пояснити мені на простому прикладі, чому мені потрібні аплікативні функтори?


Це лише посилання, яке я можу дати, але ось приємний опис застосувальних функторів із прикладами.
Хартмут П.

Відповіді:


56

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

Гарне, самодостатнє пояснення чреваті прикладами можна знайти тут . Ви також можете прочитати практичний приклад синтаксичного аналізу, розроблений Брайаном О'Салліванем, який не вимагає попередніх знань.


2
Є корисні посилання, але я не думаю, що вони можуть замінити короткий простий приклад, орієнтований на відповідь на питання, з усіма неважливими деталями, наскільки це можливо.
Дмитро Зайцев

34

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

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

f a b c d

де ви можете думати a, bі так далі , як довільні дії для запуску, і fяк функтор застосувати до результату.

f <$> a <*> b <*> c <*> d

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

Див. " Прикладне програмування з ефектами "


12

Функціональна перлина Конора Макбрайда та Росса Патерсона щодо цього стилю має кілька хороших прикладів. Це також відповідає за популяризацію стилю в першу чергу. Вони використовують термін "ідіома" для "застосувального функтора", але крім цього він цілком зрозумілий.


8

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

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

Тож обов’язково, іноді ми можемо використовувати додаткові комбінатори для того, для чого ми не можемо використовувати монадичні комбінатори. Однією з таких речей є ZipList(див. Також це запитання SO для деяких деталей ), яка є просто обгорткою навколо списків, щоб мати інший екземпляр застосунку, ніж той, що походить від екземпляра Monad списку. Документація, що застосовується, використовує такий рядок, щоб дати інтуїтивне уявлення про те ZipList, для чого:

f <$> ZipList xs1 <*> ... <*> ZipList xsn = ZipList (zipWithn f xs1 ... xsn)

Як зазначено тут , можна створити химерні екземпляри Monad, які майже працюють для ZipList.

Є й інші аплікативні функтори, які не є монадами (див. Це питання SO), і їх легко придумати. Наявність альтернативного інтерфейсу для монад - це приємно і все, але іноді створення Монади є неефективним, складним або навіть неможливим, і саме тоді вам потрібні додаткові функтори.


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


7

З мого досвіду, аплікативні функтори чудові з наступних причин:

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

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

userData = User <$> field "Name" <*> field "Address"

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

userData = do
    name <- field "Name"
    address <- field $ name ++ "'s address"
    return (User name address)

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

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

newtype Compose f g x = Compose (f (g x))

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

Нещодавно ApplicativeDoрозширення з'явилося в GHC, що дозволяє використовувати doпозначення з додатками, полегшуючи деякі нотаційні складності, якщо ви не робите жодних монадійних дій.


6

Хороший приклад: аплікативний аналіз.

Див. [Реальний світ haskell] ch16 http://book.realworldhaskell.org/read/using-parsec.html#id652517

Це код синтаксичного аналізатора з позначенням do:

-- file: ch16/FormApp.hs
p_hex :: CharParser () Char
p_hex = do
  char '%'
  a <- hexDigit
  b <- hexDigit
  let ((d, _):_) = readHex [a,b]
  return . toEnum $ d

Використання функтора робить його набагато коротшим :

-- file: ch16/FormApp.hs
a_hex = hexify <$> (char '%' *> hexDigit) <*> hexDigit
    where hexify a b = toEnum . fst . head . readHex $ [a,b]

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


4
Ви маєте дивне уявлення про "набагато коротше" - додаткова версія довша на 7 символів!
Даніель Вагнер,

@ Даніель Вагнер: -_- ||, о, мій .. ти добре розумієш код, я повинен зізнатися. Я насправді маю на увазі "вертикально коротший" :)
wuxb

Ну, це можна записати коротше , використовуючи загальні функції з бібліотеки Control.Monad: char '%' >> liftM (toEnum . fst . head . readHex) (replicateM 2 hexDigit).
HaskellElephant,

Або, використовуючи countкомбінатор від Parsec, і повертаючись до прикладного стилю:toEnum . fst . head . readHex <$> (char '%' >> count 2 hexDigit)
погладь

3

Я б також запропонував поглянути на це

В кінці статті є приклад

import Control.Applicative
hasCommentA blogComments =
BlogComment <$> lookup "title" blogComments
            <*> lookup "user" blogComments
            <*> lookup "comment" blogComments

Що ілюструє кілька особливостей аплікативного стилю програмування.

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