为什么Maybe的Semigroup实例偏向于Just,而Monoid使用Nothing作为空元素?

7

Maybe 表示在计算过程中由于错误可能不会产生结果。因此,这种计算必须被短路。

现在 Maybe 的 Semigroup/Monoid 实例似乎违反了这个语义,因为前者偏向于 Just,而后者将错误情况 Nothing 视为其空元素:

Just "foo" <> Nothing -- Just "foo"
Nothing <> Just "bar" -- Just "bar"
Just "foo" <> Just "bar" -- Just "foobar"
Nothing <> Nothing -- Nothing

在前两种情况下,我希望你能返回Nothing

这是另一种实现方式(希望它是正确/合法的):

instance Semigroup a => Semigroup (Maybe a) where
    Nothing <> _       = Nothing
    _       <> Nothing = Nothing
    Just a  <> Just b  = Just (a <> b)

instance Monoid a => Monoid (Maybe a) where
  mempty = Just mempty

我不想说这些替代方案更好。但它们也很有用。那么,为什么一开始就要做出选择,而不是让用户自己实现呢?

2
这是单子所必须满足的约束之一:存在一个空元素 0,且 0 + x = x + 0 = x - Willem Van Onsem
1
@WillemVanOnsem,是的,OP的确满足了这一点。注意:mempty = Just mempty. - luqui
@luqui:但这意味着我们对于a有一个mempty,对吗? - Willem Van Onsem
2
以下是关于编程的内容,需要将其从英语翻译成中文。请仅返回翻译后的文本:https://ghc.haskell.org/trac/ghc/ticket/1189#comment:1 这是与该问题相关的邮件列表讨论及其总结。 - max630
1
相关链接:Maybe 的幺半群Option - Daniel Wagner
显示剩余4条评论
2个回答

8

您的实例实际上是适用函子更一般实例的特例。

newtype LiftA f a = LiftA { getLiftA :: f a }

instance (Applicative f, Semigroup a) => Semigroup (LiftA f a) where
    LiftA x <> LiftA y = LiftA $ liftA2 (<>) x y

instance (Applicative f, Monoid a) => Monoid (LiftA f a) where
    mempty = LiftA $ pure mempty

我原以为这个功能在标准库中应该能找到(可能是用不同的名字),但我没有找到。然而,这个通用实例的存在可能是选择使用Maybe库版本的一个原因,因为它更像是Maybe的特殊能力。另一方面,当你的代数结构相互协调时,这是非常好的;即当一个类型是Applicative时,尽可能使用“LiftA”样式实例(在所有F-代数类上)。
另一方面,我们不能在所有地方都保持协调,因为库实例与MaybeMonadPlus实例一致。这与自然数上有两个幺半群(加法和乘法)的事实惊人地相似。对于数字,我们只是选择没有任何单子实例,因为不清楚要使用哪个。
总之,我不知道。但也许这些信息会有所帮助。

因此,在Haskell中必须做出决策/妥协,就像在命令式语言中一样。但是,可以做出决策的可能性仅限于底层数学框架允许的情况,这是一个巨大的优势 - 大概。 - user6445533
4
我认为这个问题源于类型类的核心思想,它是一种双关语,假装类型和代数结构完全对应(例如,一个类型只能是一个单子)。 这通常是正确的,但并不总是如此。 但是,这个小小的谎言使得很多事情变得非常方便,并减少了很多混乱,否则我们需要手动跟踪哪个代数结构。 但是,像这样的情况下,我们必须手动选择和/或消除歧义,这可能会很繁琐。 - luqui
你的回答和评论为一个含糊不清的问题提供了很好的解释。谢谢! - user6445533
2
“另一方面”,也许? :) - rampion

3
我认为这个想法是,Maybe应该具有仅通过Option才能准确表达的语义:它将任何semigroup都加入 Nothing作为一个“自由mempty”形式,从而使其成为monoid。也就是说,实例应该是这样的。
instance Semigroup a => Semigroup (Maybe a) where
  Nothing <> a = a
  a <> Nothing = a
  Just x <> Just y = Just $ x <> y

instance <b>Semigroup</b> a => Monoid (Maybe a) where
  mappend = (<>)
  mempty = Nothing

这类似于如何通过添加mempty<>来为任意类型提供自由幺半群,就像[]一样。

当然,这需要Semigroup类在base中,这最近才实现。

由此推论是,mempty变得清晰易懂,因为根据参数性它不能依赖于包含的类型。

实际上,这个实例可能比你建议的实例更有用,因为正如Luke所说,已经由Applicative实例涵盖了这个问题,所以你可以轻松地编写liftA2(<>)pure mempty。 当然,标准实例也已被Alternative实例涵盖,但Alternative/MonadPlus一直被认为是有点不完美的,比SemigroupMonoidFunctorApplicative更差。


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