Это аппликативный ввода-вывода реализована на основе функции от монады ввода-вывода?
В "Научи тебя Хаскеллу для великого блага!"автор утверждает, что Applicative IO
экземпляр реализован следующим образом:
instance Applicative IO where
pure = return
a <*> b = do
f <- a
x <- b
return (f x)
Я могу ошибаться, но кажется, что и return
, и do
-специфические конструкции (некоторые подслащенные связи (>>=)
) происходят из Monad IO
. Предполагая, что это правильно, мой фактический вопрос:
Почему реализация Applicative IO
зависит от Monad IO
функций/комбинаторов?
Нет Applicative
менее мощная концепция, чем Monad
?
Edit (некоторые пояснения):
Эта реализация противоречит моей интуиции, потому что согласно статье Typeclassopedia требуется, чтобы данный тип был Applicative
прежде чем это может быть сделано Monad
(или это должно быть в теории).
5 ответов:
(...) согласно статье Typeclassopedia требуется, чтобы данный тип был прикладным, прежде чем его можно будет сделать монадой (или это должно быть в теории).
Да, ваша родительская сторона-это именно тот вопрос, который здесь стоит. В теории, любой
Monad
должен также бытьApplicative
, но это на самом деле не требуется по историческим причинам (т. е. потому, чтоMonad
существует дольше). И это не единственная особенностьMonad
.Рассмотрим фактическое определения соответствующих классов типов, взятые из исходного кода пакета
base
в Hackage.Вот
Applicative
:[37]}...о котором мы можем наблюдать следующее:class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b (*>) :: f a -> f b -> f b (<*) :: f a -> f b -> f a
- контекст корректен для существующих в настоящее время классов типов, т. е. он требует
Он определяется в терминах применения функций, а не в терминах подъема кортежей (возможно, более естественных с математической точки зрения).Functor
.- он включает в себя технически лишнее операторы, эквивалентные подъемным постоянным функциям.
А пока вот
Monad
:[37]}...о котором мы можем наблюдать следующее:class Monad m where (>>=) :: m a -> (a -> m b) -> m b (>>) :: m a -> m b -> m b return :: a -> m a fail :: String -> m a
Контекст не только игнорирует
В общем, пути, которыми класс типаApplicative
, но иFunctor
, оба из которых логически подразумеваютсяMonad
, но явно не требуются.- Он также определяется в терминах применения функций, а не в более математически естественном определении, использующем
return
иjoin
.- Он включает в себя технически избыточный оператор эквивалентен снятию постоянной функции.
- Он также включает в себя
fail
, который на самом деле не вписывается вообще.Monad
отличается от математической концепции, на которой он основан, можно проследить в его истории как абстракции для программирования. Некоторые из них, такие как смещение приложения функции, которое он разделяет сApplicative
, являются отражением существующего в функциональном языке; другие, такие какfail
или отсутствие соответствующего классовый контекст-это исторические случайности больше, чем что-либо другое.Все это сводится к тому, что наличие экземпляра
Контекст класса, как иMonad
подразумевает экземпляр дляApplicative
, который, в свою очередь, подразумевает экземпляр дляFunctor
. Контекст класса просто формализует это явно; это остается верным независимо. В данном случае, учитывая экземплярMonad
, иFunctor
, иApplicative
могут быть определены совершенно общим способом.Applicative
является "менее мощным", чемMonad
в том же самом смысле, что и более general : любойMonad
автоматическиApplicative
, Если вы копируете+вставляете обобщенный экземпляр, но существуютApplicative
экземпляры, которые не могут быть определены какMonad
.Functor f => Applicative f
, говорит о двух вещах: что последнее подразумевает первое и что определение должно существовать, чтобы выполнить эту импликацию. Во многих случаях определение последнего неявно определяет первое в любом случае, но компилятор не может вывести это в целом и поэтому требует, чтобы оба экземпляра были записаны явно. То же самое можно наблюдать сEq
иOrd
- последнее, очевидно, подразумевает первое, но вам все равно нужно определить экземплярEq
, чтобы определить его дляOrd
.
Тип
IO
является абстрактным в Haskell, поэтому, если вы хотите реализовать общийApplicative
ДляIO
, вы должны сделать это с операциями, которые поддерживаются IO. Поскольку вы можете реализоватьApplicative
в терминах операцийMonad
, это кажется хорошим выбором. Можете ли вы придумать другой способ его реализации?И да,
Applicative
в некотором смысле менее мощный, чемMonad
.
Разве Аппликатив не является менее мощным понятием, чем Монада?Да, и поэтому всякий раз, когда у вас есть
В качестве аналогии, хотя цветной принтер может считаться более мощным, чем принтер в оттенках серого, вы все же можете использовать его для печати изображения в оттенках серого.Monad
, вы всегда можете сделать егоApplicative
. Вы можете заменитьIO
на любую другую монаду в вашем примере, и это будет действительный экземплярApplicative
.Конечно, можно также основать a
Monad
экземпляр наApplicative
и множествоreturn = pure
, но вы не сможете определить>>=
вообще. Вот что значит быть более могущественным.
В совершенном мире каждый
Monad
был быApplicative
(таким образом, мы имелиclass Applicative a => Monad a where ...
), но по историческим причинам оба класса типов независимы. Таким образом, ваше наблюдение, что это определение является своего рода "обратным" (использование более мощной абстракции для реализации менее мощной), верно.
У вас уже есть отличные ответы для более старых версий GHC, но в последней версии у вас действительно есть
class Applicative m => Monad m
, поэтому ваш вопрос нуждается в другом ответе.С точки зрения реализации GHC: GHC просто проверяет, какие экземпляры определены для данного типа, прежде чем пытаться скомпилировать любой из них.
С точки зрения семантики кода:
class Applicative m => Monad m
не означает, что экземплярApplicative
должен быть определен "первым", просто если он не был определен к концу вашей программы, то компилятор будет выкинуть.