Это аппликативный ввода-вывода реализована на основе функции от монады ввода-вывода?


В "Научи тебя Хаскеллу для великого блага!"автор утверждает, что 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 10

5 ответов:

(...) согласно статье Typeclassopedia требуется, чтобы данный тип был прикладным, прежде чем его можно будет сделать монадой (или это должно быть в теории).

Да, ваша родительская сторона-это именно тот вопрос, который здесь стоит. В теории, любой Monad должен также быть Applicative, но это на самом деле не требуется по историческим причинам (т. е. потому, что Monad существует дольше). И это не единственная особенность Monad.

Рассмотрим фактическое определения соответствующих классов типов, взятые из исходного кода пакета base в Hackage.

Вот Applicative:

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
[37]}...о котором мы можем наблюдать следующее:
  • контекст корректен для существующих в настоящее время классов типов, т. е. он требует Functor.
  • Он определяется в терминах применения функций, а не в терминах подъема кортежей (возможно, более естественных с математической точки зрения).
  • он включает в себя технически лишнее операторы, эквивалентные подъемным постоянным функциям.

А пока вот Monad:

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
[37]}...о котором мы можем наблюдать следующее:
    Контекст не только игнорирует 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 должен быть определен "первым", просто если он не был определен к концу вашей программы, то компилятор будет выкинуть.