Дано:
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, цитирует более раннюю версию.