为什么 May 的半群实例偏向于 Just 而 Monoid 使用 Nothing 作为其空元素?



Maybe表示由于错误而可能不会产生结果的计算。因此,此类计算必须短路。

现在Maybe的半群/幺半群实例似乎打破了这个语义,因为前者偏向于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

我不想说这些替代实例更好。但它们似乎也很有用。那么,为什么首先要进行选择,而不是将实现留给用户呢?

您的实例实际上是应用函子的更通用实例的特例。

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实例一致。 这与自然数上有两个幺半群的事实惊人地相似:加法和乘法。 对于数字,我们只是选择没有任何幺半群实例,因为不清楚使用哪个。

总之,我不知道。 但也许这些信息是有帮助的。

我认为这背后的想法是,Maybe应该具有实际上只能由Option正确表达的语义:它接受任何半群并通过添加Nothing作为临时的"自由mempty"来制作幺半群。 即实际上实例应该是

instance Semigroup a =>Semigroup (Maybe a) where
Nothing <>a = a
a <>Nothing = a
Just x <>Just y = Just $ x <>y
instanceSemigroupa =>Monoid (Maybe a) where
mappend = (<>)
mempty = Nothing

这类似于[]如何通过添加mempty<>来提供任何类型的自由幺半群。

当然,这要求Semigroup类在base,直到最近才出现。

由此推论,mempty变得明显是模式可匹配的,因为通过参数化,它不能依赖于包含的类型。

实际上,这个实例可以说比你提出的实例更有用,因为正如 Luke 所说,Applicative实例已经涵盖了它,因此您可以轻松地编写liftA2 (<>)pure mempty。诚然,标准实例也已经被Alternative实例所涵盖,但Alternative/MonadPlus一直被认为有点黑客,不如数学上更无可挑剔的SemigroupMonoidFunctorApplicative

最新更新