我试图理解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
之间的真正区别吗?
答案 0 :(得分:32)
但是你不能重写
的任何类型约束(MonadPlus m) => ...
作为Monad和Monoid的组合?
没有。在您链接的问题的最佳答案中,已经有关MonadPlus与Monoid法则的良好解释。但即使我们忽略了类型规则,也存在差异。
Monoid (m a) => ...
表示m a
必须是调用者选择的一个特定a
的幺半群,但MonadPlus m
表示m a
必须是所有a
都是monoid。所以MonadPlus a
更灵活,这种灵活性在四种情况下都很有用:
如果我们不想告诉来电者我们打算使用什么a
。
MonadPlus m => ...
代替Monoid (m SecretType) => ...
如果我们想要使用多个不同的a
MonadPlus m => ...
代替(Monoid (m Type1), Monoid (m Type2), ...) => ...
如果我们想要使用无数多个不同的a
MonadPlus m => ...
而不是不可能。
如果我们不知道我们需要什么a
。
MonadPlus m => ...
而不是不可能。
答案 1 :(得分:6)
您的guard'
与Monoid m a
类型不符。
如果您的意思是Monoid (m a)
,那么您需要为mempty
定义m ()
的内容。完成后,您已经定义了MonadPlus
。
换句话说,MonadPlus
定义了两个操作:mzero
和mplus
满足两个规则:mzero
相对于mplus
是中性的,mplus
1}}是关联的。这符合Monoid
的定义,因此mzero
为mempty
且mplus
为mappend
。
区别在于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 []
与[]
确实相同;但对于Maybe
,Monoid
实例通过人为地赋予一个标识元素来提升其下面的半群,而MonadPlus
实例则是偏左的选择;因此我们必须使用MonoidMonad First
而不是MonoidMonad Maybe
来获取正确的实例。
答案 3 :(得分:0)
编辑:于是灯泡打开了,一切都按到位。我完全误解了 Toxaris 的回答。现在我明白了,除了一些支持性示例,以及在 Alternative
类型类定义中似乎不需要 Monad
和 MonadPlus
约束的观察之外,我没有什么可添加的。
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]
)