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代数类上(。
第三方面(!(,我们不能在任何地方都有一致性,因为库实例与Maybe
的MonadPlus
实例一致。 这与自然数上有两个幺半群的事实惊人地相似:加法和乘法。 对于数字,我们只是选择没有任何幺半群实例,因为不清楚使用哪个。
总之,我不知道。 但也许这些信息是有帮助的。
我认为这背后的想法是,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
一直被认为有点黑客,不如数学上更无可挑剔的Semigroup
、Monoid
、Functor
和Applicative
。