Поскольку многие аппликативы также являются монадами, я чувствую, что в этом вопросе есть две стороны.
Зачем мне использовать аппликативный интерфейс вместо монадического, когда оба доступны?
Это в основном вопрос стиля.Хотя монады имеют синтаксический сахар в нотации do
, использование аппликативного стиля часто приводит к более компактному коду.
В этом примере у нас есть тип Foo
, и мы хотим построить случайные значения этого типа,Используя экземпляр монады для IO
, мы могли бы написать
data Foo = Foo Int Double
randomFoo = do
x <- randomIO
y <- randomIO
return $ Foo x y
Аппликативный вариант немного короче.
randomFoo = Foo <$> randomIO <*> randomIO
Конечно, мы могли бы использовать liftM2
, чтобы получитьсхожая краткость, однако аппликативный стиль более аккуратен, чем необходимость полагаться на специфичные для арности функции лифтинга.
На практике я чаще всего использую аппликативные функции почти так же, как и стиль без точек: чтобы избежатьименование промежуточных значений, когда операция более четко выражена как композиция других операций.
Зачем мне использовать аппликатив, не являющийся монадой?
Так какаппликативы более ограничены, чем монады, это означает, что вы можете извлечь более полезную статическую информацию о них.
Примером этого являются аппликативные парсеры.В то время как монадические парсеры поддерживают последовательную композицию с использованием (>>=) :: Monad m => m a -> (a -> m b) -> m b
, аппликативные парсеры используют только (<*>) :: Applicative f => f (a -> b) -> f a -> f b
.Типы делают разницу очевидной: в монадических парсерах грамматика может изменяться в зависимости от ввода, тогда как в аппликативном парсере грамматика является фиксированной.
Ограничивая таким образом интерфейс, мы можем, например, определить, является лиПарсер примет пустую строку без ее запуска .Мы также можем определить первый и последующие наборы, которые можно использовать для оптимизации или, как я недавно играл, конструировать парсеры, которые поддерживают лучшее восстановление после ошибок.