类型类MonadPlus、Alternative和Monoid之间的区别是什么?

92

标准库Haskell类型类MonadPlusAlternativeMonoid都提供了两个语义基本相同的方法:

  • 一个空值:mzeroemptymempty
  • 一个将类型类中的值合并的运算符a -> a -> amplus<|>mappend

所有三者都规定了实例应遵守的这些法律:

mempty `mappend` x = x
x `mappend` mempty = x
因此,似乎这三个类型类都提供了相同的方法。
(“Alternative”还提供“some”和“many”,但它们的默认定义通常足够满足要求,因此在这个问题上它们并不太重要。)
那么,我的疑问是:为什么会有这三个极其相似的类?除了它们不同的超类约束条件之外,它们是否存在任何真正的区别?

这是个好问题。特别是 ApplicativeMonadPlus 看起来完全相同(除了超类约束)。 - Peter
1
还有ArrowZeroArrowPlus用于箭头。我的猜测是:使类型签名更清晰(这使得不同的超类约束成为真正的差异)。 - Cat Plus Plus
4
@CatPlusPlus说:“嗯,ArrowZeroArrowPlus的种类为* -> * -> *,这意味着您可以将它们一次传递给需要多种类型使用它们的函数的箭头类型。要使用Monoid,您必须为每个特定实例要求Monoid实例,并且不能保证它们以类似的方式处理,这些实例可能是不相关的!” - Edward Kmett
1个回答

127

MonadPlusMonoid 的作用不同。

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)

左侧捕获法则

mplus (return a) b = return a

因此,任何一个MonadPlus实例都应该遵守这两个额外的法则中的一个或两个。

那么Alternative呢?

Applicative是在Monad之后定义的,逻辑上属于Monad的超类,但由于Haskell 98设计者面临的不同压力,直到2015年连Functor都不是Monad的超类。现在我们终于在GHC中将Applicative作为Monad的超类(如果还没有成为语言标准的话)。

从本质上讲,Alternative对于Applicative的作用就像MonadPlus对于Monad的作用一样。

对于这些,我们会得到

empty <*> m = empty
类似于我们在 MonadPlus 中拥有的内容,存在类似的分配和捕获属性,其中至少需要满足一个条件。

不幸的是,即使是 empty <*> m = empty 定律也太强了。例如,在 Backwards 中,这个定律就不成立!

当我们看 MonadPlus 时,empty >>= f = empty 定律几乎被强制执行。空构造中不能有任何 'a' 值来调用函数 f

然而,由于 Applicative 不是 Monad 的超类,Alternative 也不是 MonadPlus 的超类,因此我们最终将分别定义这两个实例。

此外,即使 ApplicativeMonad 的超类,你仍然需要使用 MonadPlus 类,因为即使我们遵守了

empty <*> m = empty

这并不完全足以证明那个。

empty >>= f = empty

声称某个东西是MonadPlus比声称它是Alternative更强大。

现在,按照惯例,给定类型的MonadPlusAlternative应该是一致的,但是Monoid可能会是完全不同的。

例如,对于MaybeMonadPlusAlternative做了很明显的事情:

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

总结:MonadPlusAlternative更强,而Alternative又比Monoid更强。虽然一个类型的MonadPlusAlternative实例应该是相关的,但Monoid可能是(有时是)完全不同的东西。


2
非常好的回答,但是最后一个定义似乎是错误的,它不能满足 mempty \mappend` x ≡ x`。 - Vitus
7
@EdwardKmett:这个回答似乎意味着可能有一个既是Alternative但不是MonadPlusMonad。我曾经就此提出了一个问题(https://dev59.com/wWcs5IYBdhLWcg3wHwRH),如果您知道这样的例子,我很想看到它。 - Antal Spector-Zabusky
3
能否解释一下monadplus的左抓取法则?显然,[]违反了这个法则;如果第一个参数非空,[]是否真的应该忽略第二个参数? - ben w
5
左偏分布律可能是更合理的法则,但在某些情况下并不适用。左捕捉是另一种替代法则,这些其他情况倾向于支持它,但大多数其他情况并不支持它。因此,我们实际上有两套基本无关的法则由不同的情况实现,所以 MonadPlus 实际上是两个类伪装成一个类,因为大多数人并不关心。 - Edward Kmett
6
似乎满足左零和左分配律的单子加必须忽略mplus的右参数。使用左零律,我们有:`mplus (return a) b >>= k = return a >>= k = k a`使用左分配律:`mplus (return a) b >>= k = mplus (return a >>= k) (b >>= k) = mplus (k a) (b >>= k)`因此:`mplus (k a) (b >>= k) = k a` - Bartosz Milewski
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接