Подтвердить что ты не робот

Почему MonadPlus, а не Monad + Monoid?

Я пытаюсь понять мотивацию MonadPlus. Почему это необходимо, если уже есть классы Monad и Monoid?

Конечно, экземпляры Monoid являются конкретными типами, тогда как экземпляры Monad требуют одного параметра типа. (См. Monoid vs MonadPlus для получения полезного объяснения.) Но вы не могли бы переписать ограничение типа

(MonadPlus m) => ...

как комбинация Monad и Monoid?

(Monad m, Monoid (m a)) => ...

Возьмем, например, функцию guard из Control.Monad. Его реализация:

guard :: (MonadPlus m) => Bool -> m ()
guard True = return ()
guard False = mzero

Мне удалось реализовать его, используя только Monad и Monoid:

guard' :: (Monad m, Monoid (m ())) => Bool -> m ()
guard' True = return ()
guard' False = mempty

Может кто-то прояснить реальную разницу между MonadPlus и Monad + Monoid?

4b9b3361

Ответ 1

Но вы не могли бы переписать ограничение типа

(MonadPlus m) => ...

как комбинация Monad и Monoid?

Нет. В верхнем ответе на вопрос, который вы связываете, уже есть хорошее объяснение законов MonadPlus против Monoid. Но есть различия, даже если мы игнорируем законы типа оформления.

Monoid (m a) => ... означает, что m a должен быть моноидом для одного конкретного a, выбранного вызывающим, но MonadPlus m означает, что m a должен быть моноидом для всех a. Таким образом, MonadPlus a является более гибким, и эта гибкость полезна в четырех ситуациях:

  • Если мы не хотим сообщить вызывающему, что a мы намерены использовать.
    MonadPlus m => ... вместо Monoid (m SecretType) => ...

  • Если мы хотим использовать несколько разных a.
    MonadPlus m => ... вместо (Monoid (m Type1), Monoid (m Type2), ...) => ...

  • Если мы хотим использовать бесконечно много разных a.
    MonadPlus m => ... вместо невозможного.

  • Если мы не знаем, что нам нужно a. MonadPlus m => ... вместо невозможного.

Ответ 2

Ваш guard' не соответствует типу Monoid m a.

Если вы имеете в виду Monoid (m a), то вам нужно определить, что mempty для m (). Как только вы это сделали, вы определили MonadPlus.

Другими словами, MonadPlus определяет два исключения: mzero и mplus, удовлетворяющие двум правилам: mzero является нейтральным относительно mplus, а mplus ассоциативным. Это удовлетворяет определению a Monoid, так что mzero есть mempty и mplus есть mappend.

Разница в том, что MonadPlus m является моноидом m a для любого a, но Monoid m определяет моноид только для m. Ваш guard' работает, потому что вам нужно m быть Monoid только для (). Но MonadPlus сильнее, он утверждает, что m a является моноидом для любого a.

Ответ 3

С в QuantifiedConstraints расширении языка можно выразить, что Monoid (ma) экземпляр должен быть равномерным во всех выборах: a

{-# LANGUAGE QuantifiedConstraints #-}

class (Monad m, forall a. Monoid (m a)) => MonadPlus m

mzero :: (MonadPlus m) => m a
mzero = mempty

mplus :: (MonadPlus m) => m a -> m a -> m a
mplus = mappend

В качестве альтернативы, мы можем реализовать "настоящий" класс MonadPlus для всех таких моноад-монад:

{-# LANGUAGE GeneralizedNewtypeDeriving, DerivingStrategies, QuantifiedConstraints #-}
{-# LANGUAGE UndecidableInstances #-}

import Control.Monad
import Control.Applicative

newtype MonoidMonad m a = MonoidMonad{ runMonoidMonad :: m a }
    deriving (Functor, Applicative, Monad)

instance (Applicative m, forall a. Monoid (m a)) => Alternative (MonoidMonad m) where
    empty = MonoidMonad mempty
    (MonoidMonad x) <|> (MonoidMonad y) = MonoidMonad (x <> y)

instance (Monad m, forall a. Monoid (m a)) => MonadPlus (MonoidMonad m)

Обратите внимание, что в зависимости от вашего выбора m, это может или не может дать вам MonadPlus; например, MonoidMonad [] действительно совпадает с []; но для Maybe экземпляр Monoid поднимает некоторую нижележащую полугруппу, искусственно придавая ей элемент идентичности, тогда как экземпляр MonadPlus является левосторонним выбором; и поэтому мы должны использовать MonoidMonad First вместо MonoidMonad Maybe чтобы получить правильный экземпляр.