Почему мне следует использовать аппликативные функторы в функциональном программировании?

Я новичок в Haskell и читаю о функторах и аппликативных функторах. Хорошо, я понимаю функторы и как я могу их использовать, но не понимаю, почему полезны аппликативные функторы и как я могу их использовать в Haskell. Не могли бы вы объяснить мне на простом примере, зачем мне нужны аппликативные функторы?


person Community    schedule 04.07.2011    source источник
comment
Я могу дать только ссылку, но здесь - хорошее описание аппликативных функторов с Примеры.   -  person Hartmut P.    schedule 04.07.2011
comment
Возможный дубликат: Как вы используете Control.Applicative для записи очиститель Haskell?   -  person Greg Bacon    schedule 04.07.2011


Ответы (7)


Аппликативные функторы - это конструкция, которая обеспечивает промежуточную точку между функторами и монады и поэтому более распространены, чем монады, но более полезны, чем функторы. Обычно вы можете просто отобразить функцию на функтор. Аппликативные функторы позволяют вам взять «нормальную» функцию (принимающую нефункториальные аргументы) и использовать ее для работы с несколькими значениями, которые находятся в контекстах функторов. Как следствие, это дает вам эффективное программирование без монад.

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

person Francois G    schedule 04.07.2011
comment
Есть полезные ссылки, но я не думаю, что они могут заменить короткий простой пример, нацеленный на ответ на вопрос, с удалением всех неважных деталей в максимально возможной степени. - person Dmitri Zaitsev; 27.12.2016

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

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

f a b c d

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

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

Мне нравится думать о них как о перегруженных «пробелах». Или, что обычные функции Haskell находятся в аппликативном функторе идентичности.

См. «Аппликативное программирование с эффектами»

person Don Stewart    schedule 07.07.2011

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

person John L    schedule 04.07.2011

Трудно найти примеры, где вам нужны аппликативные функторы. Я могу понять, почему промежуточный программист на Haskell задает себе этот вопрос, поскольку большинство вводных текстов представляют экземпляры, производные от монад, с использованием аппликативных функторов только в качестве удобного интерфейса.

Ключевое понимание, как упоминалось здесь и во многих введениях в эту тему, заключается в том, что аппликативные функторы находятся между функторами и монадами (даже между функторами и стрелками). Все монады являются аппликативными функторами, но не все функторы являются аппликативными.

Поэтому обязательно, иногда мы можем использовать аппликативные комбинаторы для чего-то, для чего мы не можем использовать монадические комбинаторы. Одна из таких вещей - _1 _ (см. также этот вопрос SO для некоторых деталей), который является просто оболочкой вокруг списки, чтобы иметь другой экземпляр Applicative, чем экземпляр, полученный из экземпляра списка Monad. В прикладной документации используется следующая строка, чтобы дать интуитивное представление о том, для чего предназначен ZipList:

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

Как указано здесь, это можно создавать необычные экземпляры Monad, которые почти работают с ZipList.

Существуют и другие аппликативные функторы, не являющиеся монадами (см. этот вопрос SO ) и их легко придумать. Иметь альтернативный интерфейс для монад - это хорошо и все такое, но иногда создание монады неэффективно, сложно или даже невозможно, и именно тогда вам нужны аппликативные функторы.


отказ от ответственности: создание аппликативных функторов также может быть неэффективным, сложным и невозможным. В случае сомнений проконсультируйтесь с местным теоретиком категорий для правильного использования аппликативных функторов.

person HaskellElephant    schedule 17.10.2011

По моему опыту, аппликативные функторы хороши по следующим причинам:

Определенные виды структур данных допускают мощные типы композиций, но на самом деле не могут быть превращены в монады. Фактически, большинство абстракций в функциональном реактивном программировании попадают в эту категорию. Хотя технически мы могли бы сделать, например, 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. Этого нельзя сказать о монадах, которые создают целую историю преобразователя монад, которая в некоторых неприятных отношениях усложняется. Таким образом, аппликаторы очень чистые, а это означает, что вы можете создать структуру нужного вам типа, сосредоточив внимание на небольших составных компонентах.

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

person Community    schedule 27.12.2016

Один хороший пример: аппликативный синтаксический анализ.

См. [Real world haskell] ch16 http://book.realworldhaskell.org/read/using-parsec.html#id652517

Это код парсера с do-notation:

-- 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]

«подъем» может скрыть основные детали повторяющегося кода. тогда вы можете просто использовать меньше слов, чтобы рассказать точную историю.

person wuxb    schedule 04.07.2011
comment
У вас есть странное представление о том, что гораздо короче - аппликативная версия на 7 символов длиннее! - person Daniel Wagner; 14.07.2011
comment
@ Даниэль Вагнер: -_- || О боже ... ты хорошо разбираешься в коде, должен признаться. Я имею в виду «короче по вертикали» :) - person wuxb; 14.07.2011
comment
Что ж, его можно написать короче, используя общие функции из библиотеки Control.Monad: char '%' >> liftM (toEnum . fst . head . readHex) (replicateM 2 hexDigit). - person HaskellElephant; 17.10.2011
comment
Или, используя комбинатор count из Parsec и вернувшись к аппликативному стилю: toEnum . fst . head . readHex <$> (char '%' >> count 2 hexDigit) - person pat; 18.10.2011

Я также предлагаю взглянуть на это

В конце статьи пример

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

Это иллюстрирует некоторые особенности стиля прикладного программирования.

person Vlad Patryshev    schedule 29.02.2012