Revamping the numeric classes

Marcin 'Qrczak' Kowalczyk qrczak@knm.org.pl
7 Feb 2001 18:31:02 GMT


Wed, 7 Feb 2001 13:04:12 +0100 (MET), Bjorn Lisper <lisper@it.kth.se> pisze:

> A natural principle to adopt is that an already typeable expression should
> not be transformed. This will for instance resolve the ambiguity in the list
> of list example: if l :: [[a]] then length l is already well-typed and
> should not be transformed into map length l.

So there are ways to interpret an expression which are not chosen
only because some other way is a better match? This is very dangerous
in principle.

Two interpretations of a code are "correct", but one is "more correct"
than the other.

Say there is a code which relies on the implicit fmap, and it's
slightly changed by replacing the function with a more general
function, which has the same result on this instance. Then suddenly
without a warning the code has a different meaning, because it is
now applied in a different way (different placement of implicit fmaps).

Also, what is the inferred type of, for example
    f x y = x + length y
? It can be
    Int -> [a] -> Int
    [Int] -> [a] -> [Int]
and neither is more general than the other. And this is a simple
function.

> >Again, Haskell does not have subtyping. It is not compatible with type
> >inference - it can only work in poor languages which require an operation
> >to be fully applied where it is used, and either don't have static types
> >or require them to be specified explicitly.
> 
> I am not so sure about this. Could you exemplify?

Sorry, I don't have a concrete example in mind. How to infer types when
implicit conversions are possible anywhere? The above function f can
be applied even to two numbers (because the second would be promoted
to a list of length = 1), so what is its inferred most general type?

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, because a programmer might mean
the other alternative - there is no simple way to ensure that the
compiler interprets it in the same way as I wanted. It's not enough
to check that all types match modulo conversions - I must carefully
check that no "better" interpretation is possible.

> Note that you can do some of this overloading already within
> Haskell's class system.

But it's quite rigorous: all uses of an identifier must be at a type
which is an instance of a single generic type. There is enough type
information to disambiguate the meaning in most cases - *without*
rejecting an interpretation because another was better (except
defaulting of numeric types - yes, it's ugly).

Another advantage of the Haskell's class system is that no code
relies on absence of something. Adding an instance does not make
previously working code ambiguous or otherwise incorrect! Except when
the instance conflicts with another one - this is the only kind of
"negative constraint".

-- 
 __("<  Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/
 \__/
  ^^                      SYGNATURA ZASTĘPCZA
QRCZAK