Различие между классами MonadPlus, Alternative и Monoid?

Стандартные библиотеки Haskell typeclasses MonadPlus, Alternative и Monoid каждый из них предоставляют два метода с по существу той же семантикой:

  • Пустое значение: mzero, empty или mempty.
  • Оператор a -> a -> a, который объединяет значения в классе типов: mplus, <|> или mappend.

Все три определяют эти законы, к которым следует придерживаться экземпляры:

mempty `mappend` x = x
x `mappend` mempty = x

Таким образом, кажется, что все три класса имеют одинаковые методы.

(Alternative также предоставляет some и many, но их определения по умолчанию обычно достаточны, и поэтому они не слишком важны с точки зрения этого вопроса.)

Итак, мой запрос: почему эти три чрезвычайно похожих класса? Есть ли какая-то реальная разница между ними, помимо их различных ограничений суперкласса?

Ответ 1

MonadPlus и Monoid служат для разных целей.

A Monoid параметризуется по типу вида *.

class Monoid m where
    mempty :: m
    mappend :: m -> m -> m

и поэтому он может быть создан для почти любого типа, для которого существует очевидный оператор, ассоциативный и имеющий единицу.

Однако MonadPlus не только указывает, что у вас есть моноидальная структура, но также и то, что эта структура связана с тем, как работает Monad, и что эта структура не заботится о ценности, содержащейся в монаде, это (частично) обозначается тем фактом, что MonadPlus принимает аргумент вида * -> *.

class Monad m => MonadPlus m where
    mzero :: m a
    mplus :: m a -> m a -> m a

В дополнение к моноидным законам мы имеем два возможных набора законов, которые мы можем применить к MonadPlus. К сожалению, сообщество не соглашается с тем, что они должны быть.

По крайней мере, мы знаем

mzero >>= k = mzero

но есть еще два конкурирующих расширения, левый (sic) закон распределения

mplus a b >>= k = mplus (a >>= k) (b >>= k)

а левый закон catch

mplus (return a) b = return a

Таким образом, любой экземпляр MonadPlus должен удовлетворять одному или двум из этих дополнительных законов.

Итак, что насчет Alternative?

Applicative был определен после Monad и логически принадлежит суперклассу Monad, но в значительной степени из-за разного давления на дизайнеров в Haskell 98 даже Functor не был суперклассом Monad до 2015 года. Теперь мы, наконец, имеем Applicative в качестве суперкласса Monad в GHC (если еще не в стандарте языка.)

Эффективно, Alternative соответствует Applicative, что MonadPlus равно Monad.

Для этого мы получим

empty <*> m = empty

аналогично тому, что у нас есть с MonadPlus, и существуют аналогичные свойства дистрибутива и catch, по крайней мере один из которых вы должны удовлетворить.

К сожалению, даже закон empty <*> m = empty слишком силен. Например, это не относится к Backwards!

Когда мы смотрим на MonadPlus, пустой нам → = f = пустой закон. Пустая конструкция не может иметь никакого а в ней, чтобы вызвать функцию f с любыми способами.

Однако, поскольку Applicative не является суперклассом Monad, а Alternative не является суперклассом MonadPlus, мы завершаем определение обоих экземпляров отдельно.

Более того, даже если Applicative был суперклассом Monad, вы все равно нуждаетесь в классе MonadPlus, потому что даже если бы мы выполнили

empty <*> m = empty

что недостаточно строго, чтобы доказать, что

empty >>= f = empty

Так что утверждать, что что-то есть MonadPlus, сильнее, чем утверждать, что это Alternative.

Теперь, по соглашению, MonadPlus и Alternative для данного типа должны совпадать, но Monoid может быть совершенно другим.

Например, MonadPlus и Alternative для Maybe делают очевидную вещь:

instance MonadPlus Maybe where
    mzero = Nothing
    mplus (Just a) _  = Just a
    mplus _        mb = mb

но экземпляр Monoid поднимает полугруппу в Monoid. К сожалению, в то время, когда в Haskell 98 не существовал класс Semigroup, он делает это, переставляя Monoid, но не используя его блок. ಠ_ಠ

instance Monoid a => Monoid (Maybe a) where
    mempty = Nothing
    mappend (Just a) (Just b) = Just (mappend a b)
    mappend Nothing x = x
    mappend x Nothing = x
    mappend Nothing Nothing = Nothing

TL; DR MonadPlus является более сильным выражением, чем Alternative, что в свою очередь является более сильным выражением, чем Monoid, а экземпляры MonadPlus и Alternative для тип должен быть связан, Monoid может быть (и иногда есть) чем-то совершенно другим.