For what Alt in Monoid instance needed?

140 views Asked by At

In Monoid and Semigroup instances of Alternative Alt used.

Why we can't write instance without it?

{-# LANGUAGE FlexibleInstances #-}
instance Alternative f => Semigroup (f a) where
  (<>) = <|>

instance Alternative f => Monoid (f a) where
  mempty = empty

And if we can write that, can we then replace Alternative with (Monoid (f a), Applicative f) in functions?

3

There are 3 answers

2
Iceland_jack On BEST ANSWER

You use it for deriving Monoid for any Alternative

{-# Language DerivingVia #-}

data F a = ..
  deriving (Semigroup, Monoid)
  via Alt F a

instance Functor     F where ..
instance Applicative F where ..
instance Alternative F where ..

Alt is a newtype for good reason as there are many ways to describe Monoid behaviour for an applied type f a. For example Applicative lifting: Ap.

{-# Language DerivingVia #-}

data G a = ..
  deriving (Semigroup, Monoid, Num, Bounded)
  via Ap G a

instance Functor     G where ..
instance Applicative G where ..

The instances you give are maximally overlapping, the Monoid instance of any applied type is now forced to be the Alternative instance, completely ignoring the a parameter.

There are many instances where this would not be correct, for example Semigroup a => Semigroup (Maybe a) is not the same as the Semigroup you would get from Alternative Maybe.

It is possible using a rather new feature QuantifiedConstraints to quantify over the argument of a type constructor forall x. Monoid (f x). This is not the same as Alternative but similar

{-# Language QuantifiedConstraints #-}
..

type Alternative' :: (Type -> Type) -> Constraint

class    (forall x. Monoid (f x)) => Alternative' f
instance (forall x. Monoid (f x)) => Alternative' f
0
willeM_ Van Onsem On

You use the (<|>) :: Alternative f => f a -> f a -> f a and empty :: Alternative f => f a. As the signatures suggest, these are defined for items of type Applicative f => f a. You thus can only use these functions to process such items, and this thus requires that if you work with an f a, for eample to define mempty :: f a with mempty = empty, then that requires that f is a member of the Alternative typeclass.

That being said, while a lot data types can have a Semigroup and Monoid instance defined as how the Alternative is implemented, this is not per se the best instance. Yes Alternative is a monoid on applicative functors, but that should not per se be the monoid instance on these data types.

0
leftaroundabout On

If the instances you propose existed in that form, every type that matches f a would immediately defer to it. That includes types where it makes sense, but consider

newtype ResultsSum a = ResultsSum {funToSum :: a -> Int}

instance Semigroup (ResultsSum a) where
  ResultsSum p <> ResultsSum q = ResultsSum $ \x -> p x + q x

Unfortunately though, ResultsSum a matches f a. But it is not an Alternative; it isn't even a functor, and can't be (rather, it is Contravariant). However, the compiler doesn't take that into account when resolving instances: it just sees two instance declarations whose heads both purport to enable ResultsSum being a semigroup, which triggers an ambiguous-instance error.

Granted, this example could be addressed with {-# OVERLAPPING #-} pragmas, but it's always best to avoid instance overlaps as they can lead to strange. It's unnecessary, since you can also also derive those instances via the Alternative one. Though I personally would actually rather do it the other way around: define the Monoid instance first and then Alternative in terms of it.