
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