3
votes

Let's say I want to define my own type classes for semigroup and monoid. So I write this code:

class Semigroup g where
    (<>) :: g -> g -> g

class Semigroup m => Monoid m where
    mempty :: m

But there's another way I could define the relationship between those type classes, with some extensions:

class Semigroup g where
    gappend :: g -> g -> g

class Monoid m where
    mempty :: m
    mappend :: m -> m -> m

instance Monoid m => Semigroup m where
    gappend = mappend

The latter design has an advantage - Later I can add more instances for Monoid. For example, if I have a typeclass for a vector space I can later make it an additive group without having to specify it in the class declaration. On the other hand, I am forced to use flexible instances and undecidable instances.

My question is - what is the best design for this particular case?

1
better is a really difficult term here - the question I had would be: why do you redefine it at all? Having said this: I like the first a lot more (the undecidable isntances are a red flag IMO) - Random Dev
I want to write my own library for linear algebra and want to have some comfortable classes such as vector space and ring. Monoid and Semigroup are used only as an example. - Brrch
yeah fair I guess - but it's really not the best question for SO (you probably get a few close flags because it seems a bit opinion-based on the surface) - maybe a better idea is to go do reddit or the haskell IRC channels/mailing lists to discuss this (?) - especially on the later you will have lot's of really smart people that will tell you in no time why the one or the other is the way to go ;) - Random Dev
I really don't see what the advantage is. Could you clarify exactly what did you earn with the second code with respect to the first at the cost of allowing undecidable instances? - Bakuriu
In my opinion flexible and undecidable instances are harmless, in practice. However, the second approach requires overlapping instances if you want some semigroups which are not monoids, and that is where the real pain comes from. Overlapping and incoherent instances have a quite subtle semantics, which is hard to grasp, and can easily lead to unwanted surprises. - chi

1 Answers

4
votes

The first version says "monoids are semigroups, with the additional property mempty".

The second certain says "all types are semigroups, provided they are also monoids". Unless you're happy to turn on overlapping instances you can't add any other instances, so this amounts to "a type is a semigroup if and only if it is a monoid"; exactly backwards from the true relationship.

I would almost always prefer the former. Yes, it forces someone wanting to add a Monoid instance to also write a Semigroup instance, but the real "work" they have to do is the same either way: decide on implementations for mempty and mappend. The only thing you save them from by using the second approach is a little boilerplate.