Reputation:
Maybe
expresses computations that might not produce a result due to an error. Therefore such computations must be short circuited.
Now Maybe
's Semigroup/Monoid instances seem to break with this semantics, because the former is biased towards Just
and the latter treats the error case Nothing
as its empty element:
Just "foo" <> Nothing -- Just "foo"
Nothing <> Just "bar" -- Just "bar"
Just "foo" <> Just "bar" -- Just "foobar"
Nothing <> Nothing -- Nothing
I would expect Nothing
for the first two cases.
Here is the alternative implementation (hopefully it is correct/lawful):
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
I don't want to say that these alternative instances are better. But they seem useful too. So why was a selection made in the first place instead of leaving the implementation to the user?
Upvotes: 7
Views: 1245
Reputation: 120711
I think the idea behind this is that Maybe
is supposed to have the semantics that are actually only properly expressed by Option
: it takes any semigroup and makes a monoid out of it, by adding Nothing
as an ad-hoc “free mempty”. I.e. actually the instances should be
instance Semigroup a => Semigroup (Maybe a) where
Nothing <> a = a
a <> Nothing = a
Just x <> Just y = Just $ x <> y
instance Semigroup a => Monoid (Maybe a) where
mappend = (<>)
mempty = Nothing
This is analogous to how []
gives the free monoid over any type, by adding both mempty
and <>
.
Of course, this requires that the Semigroup
class is in base
, which it wasn't until recently.
Corollary to this is that mempty
becomes clearly pattern-matchable, because by parametricity it cannot depend on the contained type.
Practically speaking, this instance is arguably more useful then the one you propose, because as Luke remarked that is already covered by the Applicative
instance so you can easily write liftA2 (<>)
and pure mempty
. Granted, the standard instance is also already covered by the Alternative
instance, but Alternative/MonadPlus
have always been considered as a bit hackish, inferior to the more mathematically impeccable Semigroup
, Monoid
, Functor
and Applicative
.
Upvotes: 3
Reputation: 60463
Your instance is actually a special case of a much more general instance for applicative functors.
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
Which I assumed would be in the standard library somewhere (under a different name probably) but I couldn't find it. But the existence of this general instance could be one reason to choose the library version of Maybe
, which is more of Maybe
's special power. On the other hand, it's quite nice when your algebraic structures are all coherent with each other; i.e. when a type is an Applicative
, use the "LiftA
"-style instance whenever possible (on all F-algebra classes).
On the third hand (!), we can't have coherence everywhere, since the library instance agrees with Maybe
's MonadPlus
instance. This is strikingly parallel to the fact that there are two monoids on natural numbers: addition and multiplication. For numbers, we just picked not to have any monoid instance because it's unclear which to use.
In conclusion, I don't know. But maybe this information was helpful.
Upvotes: 8