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

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

Ответ 1

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

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

Ответ 2

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

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

f a b c d

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

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

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

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

Ответ 3

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

Ответ 4

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

Ключевое понимание, как упоминалось здесь и в большинстве представлений по этому вопросу, заключается в том, что Аппликативные Функторы находятся между Функторами и Монадами (даже между Функторами и Стрелками). Все Монады являются аппликативными функторами, но не все Функторы являются аппликативными.

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

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

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

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


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

Ответ 5

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

См. [real 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]

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

Ответ 6

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

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

Отсутствие зависимости от данных в аппликативном функторе позволяет вам, например, перейдите к действию, ища все эффекты, которые он может произвести, не имея данных. Таким образом, вы можете представить аппликацию "веб-формы", используемую так:

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

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

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

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

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

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

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

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

Ответ 7

Я также предложил бы взглянуть на this

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

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

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