A sample revised prelude for numeric classes

Dylan Thurston dpt@math.harvard.edu
Mon, 12 Feb 2001 11:59:04 -0500


On Mon, Feb 12, 2001 at 05:24:37PM +1300, Brian Boutel wrote:
> > Thus these laws should be interpreted as guidelines rather than
> > absolute rules.  In particular, the compiler is not allowed to use
> > them.  Unless stated otherwise, default definitions should also be
> > taken as laws.
> 
> Including laws was discussed very early in the development of the
> language, but was rejected. IIRC Miranda had them. The argument against
> laws was that their presence might mislead users into the assumption
> that they did hold, yet if they were not enforcable then they might not
> hold and that could have serious consequences. Also, some laws do not
> hold in domains with bottom, e.g. a + (negate a) === 0 is only true if a
> is not bottom. 

These are good points, but I still feel that laws can be helpful as
guidelines, as long as they are not interpreted as anything more.  For
instance, the Haskell Report does give laws for Monads and quotRem,
although they, too, are not satisfied in the presence of bottom, etc.
(Is that right?)

Writing out the laws lets me say, for instance, whether users of Num
and Fractional should expect multiplication to be commutative.  (No
and yes, respectively.  I require Fractional to be commutative mainly
because common usage does not use either '/' or 'reciprocal' to
indicate inverse in a non-commutative ring.)

> > class (Additive a) => Num a where
> >     (*)         :: a -> a -> a
> >     one         :: a
> >     fromInteger :: Integer -> a
> >
> >       -- Minimal definition: (*), one
> >     fromInteger 0         = zero
> >     fromInteger n | n < 0 = negate (fromInteger (-n))
> >     fromInteger n | n > 0 = reduceRepeat (+) one n
> 
> This definition requires both Eq and Ord!!!

Ah, but only Eq and Ord for Integer, which (as a built-in type) has Eq
and Ord instances.  The type signature for reduceRepeated is

  reduceRepeated :: (a -> a -> a) -> a -> Integer -> a

> As does this one:
> > class (Num a, Additive b) => Powerful a b where
> >     (^) :: a -> b -> a
> > instance (Num a) => Powerful a (Positive Integer) where
> >     a ^ 0 = one
> >     a ^ n = reduceRepeated (*) a n
> > instance (Fractional a) => Powerful a Integer where
> >     a ^ n | n < 0 = recip (a ^ (negate n))
> >     a ^ n         = a ^ (positive n)

Likewise here.

> and several others further down. 

I tried to be careful not to use Eq and Ord for generic types when not
necessary, but I may have missed some.  Please let me know.

(Oh, I just realised that Euclid's algorithm requires Eq.  Oops.
That's what I get for not writing it out explicitly.  I'll have to
revisit the Integral part of the hierarchy.)

> > (4) In some cases, the hierarchy is not finely-grained enough:
> >     operations that are often defined independently are lumped
> >     together.  For instance, in a financial application one might want
> >     a type "Dollar", or in a graphics application one might want a
> >     type "Vector".  It is reasonable to add two Vectors or Dollars,
> >     but not, in general, reasonable to multiply them.  But the
> >     programmer is currently forced to define a method for (*) when she
> >     defines a method for (+).
> 
> Why do you stop at allowing addition on Dollars and not include
> multiplication by a scalar? Division is also readily defined on Dollar
> values, with a scalar result, but this, too, is not available in the
> proposal. 

I will allow multiplication by a scalar; it's just not in the classes
I've written down so far.  (And may not be in the Prelude.)

Thanks for reminding me about division.  I had forgotten about that.
It bears some thought.

> Having Units as types, with the idea of preventing adding Apples to
> Oranges, or Dollars to Roubles, is a venerable idea, but is not in
> widespread use in actual programming languages. Why not?

That's a good question.  I don't know.  One cheeky answer would be for
lack of a powerful enough type system (allowing you to, e.g., work on
generic units when you want to), but I don't know if that is actually
true.

Don't modern HP calculators use units consistently?

> Vectors, too, can be multiplied, producing both scalar- and
> vector-products.

Yes, but these are really different operations and should be
represented with different symbols.  Neither one is associative, for
instance.

> It seems that you are content with going as far as the proposal permits,
> though you cannot define, even within the revised Class system, all the
> common and useful operations on these types. This is the same situation
> as with Haskell as it stands. The question is whether the (IMHO)
> marginal increase in flexibility is worth the cost.

I believe that with this structure as base, the other common and
useful operations can easily be added on top.

But I should go ahead and do it.

Best,
	Dylan Thurston