Revamping the numeric classes

Tom Pledger
Thu, 8 Feb 2001 21:00:58 +1300 (NZDT)

Dylan Thurston writes:
 | (A question in the above context is whether the literal '0' should
 | be interpreted as 'fromInteger (0::Integer)' or as 'zero'.
 | Opinions?)

Opinions?  Be careful what you wish for.  ;-)

In a similar discussion last year, I was making wistful noises about
subtyping, and one of Marcin's questions

was whether the numeric literal 10 should have type Int8 (2's
complement octet) or Word8 (unsigned octet).  At the time I couldn't
give a wholly satisfactory answer.  Since then I've read the oft-cited
paper "On Understanding Types, Data Abstraction, and Polymorphism"
(Cardelli & Wegner, ACM Computing Surveys, Dec 1985), which suggests a
nice answer: give the numeric literal 10 the range type 10..10, which
is defined implicitly and is a subtype of both -128..127 (Int8) and
0..255 (Word8).

The differences in arithmetic on certain important range types could
be represented by multiple primitive functions (or perhaps foreign
functions, through the FFI):

    primAdd   :: Integer -> Integer -> Integer    -- arbitrary precision
    primAdd8s :: Int8    -> Int8    -> Int8       -- overflow at -129, 128
    primAdd8u :: Word8   -> Word8   -> Word8      -- overflow at -1, 256
    -- etc.

    instance Additive Integer where
        zero = 0
        (+)  = primAdd

...with similar instances for the integer subrange types which may
overflow.  These other instances would belong outside the standard
Prelude, so that the ambiguity questions don't trouble people (such as
beginners) who don't care about the space and time advantages of fixed
precision integers.

Subtyping offers an alternative approach to handling arithmetic
  - Use only arbitrary precision arithmetic.
  - When calculated result *really* needs to be packed into a fixed
    precision format, project it (or treat it down, etc., whatever's
    your preferred name), so that overflows are represented as

For references to other uses of  class Subtype  see:

For a reference to some unification-driven rewrites, see:

Marcin 'Qrczak' Kowalczyk writes:
 | Assuming that Ints can be implicitly converted to Doubles, is the
 | function
 |     f :: Int -> Int -> Double -> Double
 |     f x y z = x + y + z
 | ambiguous? Because there are two interpretations:
 |     f x y z = realToFrac x + realToFrac y + z
 |     f x y z = realToFrac (x + y) + z
 | Making this and similar case ambiguous means inserting lots of explicit
 | type signatures to disambiguate subexpressions.
 | Again, arbitrarily choosing one of the alternatives basing on some
 | set of weighting rules is dangerous,

I don't think the following disambiguation is too arbitrary:

        x + y + z                 -- as above

    --> (x + y) + z               -- left-associativity of (+)

    --> realToFrac (x + y) + z    -- injection (or treating up) done
                                  -- conservatively, i.e. only where needed