为什么MonadPlus而不是Monad + Monoid?

时间:2014-04-11 23:02:42

标签: haskell monads monoids

我试图理解MonadPlus背后的动机。如果已经存在类型MonadMonoid

,为什么还有必要?

当然,Monoid的实例是具体类型,而Monad的实例需要单个类型参数。 (请参阅Monoid vs MonadPlus以获得有用的解释。)但是,您无法重写

的任何类型约束
(MonadPlus m) => ...

MonadMonoid的组合?

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

例如,从guard获取Control.Monad功能。它的实现是:

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

我只能使用MonadMonoid

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

有人可以澄清MonadPlusMonad + Monoid之间的真正区别吗?

4 个答案:

答案 0 :(得分:32)

  

但是你不能重写

的任何类型约束
(MonadPlus m) => ...
     

作为Monad和Monoid的组合?

没有。在您链接的问题的最佳答案中,已经有关MonadPlus与Monoid法则的良好解释。但即使我们忽略了类型规则,也存在差异。

Monoid (m a) => ...表示m a必须是调用者选择的一个特定a的幺半群,但MonadPlus m表示m a必须是所有a都是monoid。所以MonadPlus a更灵活,这种灵活性在四种情况下都很有用:

  1. 如果我们不想告诉来电者我们打算使用什么a
    MonadPlus m => ...代替Monoid (m SecretType) => ...

  2. 如果我们想要使用多个不同的a MonadPlus m => ...代替(Monoid (m Type1), Monoid (m Type2), ...) => ...

  3. 如果我们想要使用无数多个不同的a MonadPlus m => ...而不是不可能。

  4. 如果我们不知道我们需要什么aMonadPlus m => ...而不是不可能。

答案 1 :(得分:6)

您的guard'Monoid m a类型不符。

如果您的意思是Monoid (m a),那么您需要为mempty定义m ()的内容。完成后,您已经定义了MonadPlus

换句话说,MonadPlus定义了两个操作:mzeromplus满足两个规则:mzero相对于mplus是中性的,mplus 1}}是关联的。这符合Monoid的定义,因此mzeromemptymplusmappend

区别在于MonadPlus m对于任何m a都是幺半群a,但Monoid m仅为m定义了一个幺半群。您的guard'有效,因为您只需要m成为Monoid ()。但MonadPlus更强,它声称m a是任何a的幺半群。

答案 2 :(得分:0)

使用the QuantifiedConstraints language extension,您可以表示Monoid (m a)实例在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

或者,我们可以为所有此类monoid-monad一般实现“ real” 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 [][]确实相同;但对于MaybeMonoid实例通过人为地赋予一个标识元素来提升其下面的半群,而MonadPlus实例则是偏左的选择;因此我们必须使用MonoidMonad First而不是MonoidMonad Maybe来获取正确的实例。

答案 3 :(得分:0)

编辑:于是灯泡打开了,一切都按到位。我完全误解了 Toxaris 的回答。现在我明白了,除了一些支持性示例,以及在 Alternative 类型类定义中似乎不需要 MonadMonadPlus 约束的观察之外,我没有什么可添加的。

monadPlusExample :: (MonadPlus m) => m Int
monadPlusExample = do
  x <- idM 7
  f <- idM (* 6)
  return $ f x
  where
    idM a = return a `mplus` mzero

-- a Monoid constraint for each lifted type
monoidExample :: (Monad m, Monoid (m Int), Monoid (m (Int -> Int))) => m Int
monoidExample = do
  x <- idM 7
  f <- idM (* 6)
  return $ f x
  where
    idM a = return a <> mempty

-- yes, QualifiedConstraints unifies the Monoid constraint quite nicely
monoidExample2 :: (Monad m, forall a. Monoid (m a)) => m Int
monoidExample2 = do
  x <- idM 7
  f <- idM (* 6)
  return $ f x
  where
    idM a = return a <> mempty

为了被调用,它们必须被输入一个具体的类型(例如 monoidExample2 :: [Int]