
On Mon, Jul 25, 2011 at 1:40 PM, Jacques Carette
** On 25/07/2011 9:55 AM, Edward Kmett wrote:
If you have an associative (+), then you can use (.*) to multiply by a whole number, I currently do fold a method into the Additive class to 'fake' a LeftModule, but you have to explicitly use it.
class Additive m => LeftModule r m class LeftModule Whole m => Additive m
This says that if you have an Additive semigroup, then there exists a LeftModule over the whole numbers, and that every leftmodule is additive, but there can exist other LeftModules than just ones over the whole numbers!
Given LeftModule Integer m, you'd know you have Additive m and LeftModule Whole m.
LeftModule Integer m => LeftModule Whole m <=> Additive m.
I believe that part of the issue here is that you are using a single relation (given by class-level => ) to model what are actually two different relations: a 'constructive' inclusion and a 'view' (to use the terminology from the Specifications community, which is clearly what these class definitions are).
Just like inheritance hierarchies fail miserably when you try to model more than one single relation, it should not be unsurprising that the same thing befalls '=>', which is indeed a (multi-ary) relation.
In my own hierarchy for Algebra, I use two relations. Slightly over-simplifying things, one of them reflects 'syntactic' inclusion while the other models 'semantic' inclusion. There are very strict rules for the 'syntactic' one, so that I get a nice hierarchy and lots of free theorems, while the semantic one is much freer, but generates proof obligations which must be discharged. The syntactic one generates a nice DAG (with lots of harmless diamonds), while the semantic one is a fully general graph.
Here is another way to look at it: when you say
class LeftModule Whole m => Additive m you are closer to specifying an *instance* relation than a *class constraint* relation.
This is very true. However, as mentioned at the outset specifying such instance requires newtype noise (on m, to avoid incoherent overlap with the other instances) that leads to a particularly hideous programming style. newtype NaturalModule m = NaturalModule { runNaturalModule :: m } instance Monoidal m => LeftModule Natural (NaturalModule m) where It isn't so bad when working with simple examples like fiveTimes m = runNaturalModule (5 .* NaturalModule m) but it gets progressively worse as the hierarchy gets deeper, and I have to deal with putting on and taking off more and more of these silly wrappers. Placing the superclass constraint enforces that such instances will always be available to me, and admits optimized implementations, which I currently have to shoehorn into the main class and expose by convention. -Edward