Дано:
Applicative m, Monad m => mf :: m (a -> b), ma :: m a
он считается законным, что:
mf <*> ma === do { f <- mf; a <- ma; return (f a) }
или более кратко:
(<*>) === ap
Документация для Control.Applicative
говорит, что <*>
является "последовательным приложением", и это говорит о том, что (<*>) = ap
. Это означает, что <*>
должен последовательно оценивать эффекты слева направо, для соответствия с >>=
... Но это неправильно. МакБрайд и оригинальная статья Патерсона, кажется, подразумевают, что последовательность слева направо произвольна:
Мода IO, и даже любая Монада, могут быть сделаны Аппликативными, взяв
pure
=return
и<*>
=ap
. В качестве альтернативы мы могли бы использовать вариантap
, который выполняет вычисления в обратном порядке, но мы будем придерживаться порядка слева направо в этой статье.
Таким образом, существуют два законных нетривиальных дифференцирования для <*>
, которые следуют из >>=
и return
, с отличным поведением. И в некоторых случаях ни один из этих двух выводов не является желательным.
Например, закон (<*>) === ap
устанавливает Data.Validation для определения двух разных типов данных: Validation
и AccValidation
. Первый имеет экземпляр Monad
, похожий на ExceptT, и последовательный экземпляр Applicative
, который имеет ограниченную полезность, поскольку он останавливается после первая ошибка. Последнее, с другой стороны, не определяет экземпляр Monad
и поэтому свободно реализует Applicative
, который гораздо полезнее накапливает ошибки.
В StackOverflow было несколько обсуждений об этом, но я не думаю, что это действительно дошло до мяса вопроса:
Почему это должен быть закон?
Другие законы для функторов, аппликаций и монад, таких как идентичность, ассоциативность и т.д., выражают некоторые фундаментальные, математические свойства этих структур. Мы можем реализовать различные оптимизации с использованием этих законов и доказать, что они используют наш собственный код. Напротив, мне кажется, что закон (<*>) === ap
накладывает произвольное ограничение без соответствующей выгоды.
Для чего это стоит, я предпочел бы отказаться от закона в пользу чего-то вроде этого:
newtype LeftA m a = LeftA (m a)
instance Monad m => Applicative (LeftA m) where
pure = return
mf <*> ma = do { f <- mf; a <- ma; return (f a) }
newtype RightA m a = RightA (m a)
instance Monad m => Applicative (RightA m) where
pure = return
mf <*> ma = do { a <- ma; f <- mf; return (f a) }
Я думаю, что правильно фиксирует взаимосвязь между ними, без чрезмерного ограничения.
Итак, несколько углов, чтобы подойти к вопросу:
- Существуют ли другие законы, относящиеся к
Monad
иApplicative
? - Есть ли какая-либо неотъемлемая математическая причина для эффектов для последовательности для
Applicative
так же, как и дляMonad
? - Может ли GHC или какой-либо другой инструмент выполнять преобразования кода, которые предполагают/требуют, чтобы этот закон был истинным?
- Почему предложение Functor-Applicative-Monad считается такой превосходно хорошей? (Цитаты были бы высоко оценены здесь).
И один бонусный вопрос:
- Как
Alternative
иMonadPlus
подходят ко всему этому?
Примечание: главное изменение, чтобы прояснить мясо вопроса. Ответ, отправленный @duplode, цитирует более раннюю версию.