opPow, opDollar

Matti Niemenmaa see_signature at for.real.address
Sun Nov 8 10:48:47 PST 2009


Stewart Gordon wrote:
> Matti Niemenmaa wrote:
> <snip>
>> It's essentially because Haskell has separate type classes (kiiiinda 
>> like D interfaces... I won't go into that topic) for integers, 
>> fractional numbers, and floating-point numbers. In D the types of 
>> those three operators could be something like:
>>
>> anyinteger     ^(anyinteger    base, anyinteger  exponent);
>> anyfractional ^^(anyfractional base, anyinteger  exponent);
>> anyfloating   **(anyfloating   base, anyfloating exponent);
> <snip>
> 
> You've merely expanded on what I'd already made out - it doesn't explain 
> why these generic functions can't share the same name.  Is it because 
> Haskell doesn't support function overloading as D does, or for some 
> other reason?

The former. Haskell does function overloading via type classes.

I think that the reason why these functions can't have the same name is 
that they should all have a single, well-defined type and value. If 
they're all called 'pow', what is the type of pow? It can't have all 
three types at once, that makes no sense. And what happens if I give pow 
to a higher-order function: which one does it end up calling? You'd need 
some kind of notation to disambiguate. The developers of Haskell 
evidently opted to simply force differently-typed values to have 
different names, instead of being able to give them all the same name 
but then having to qualify which one you mean whenever you use it. 
That'd pretty much amount to them having different names anyway, I think.

Just to show that this quality of Haskell isn't very limiting in 
practice, a somewhat tangential explanation of the way these 
exponentiation functions are overloaded follows.

The types of these functions in Haskell are (read '::' as 'has type', 
the type after the last '->' as the return value and the others as the 
parameters):

(^)  :: (Num a, Integral b)        => a -> b -> a
(^^) :: (Fractional a, Integral b) => a -> b -> a
(**) :: (Floating a)               => a -> a -> a

The part before the '=>' is the class context, restricting the type 
variables 'a' and 'b'. 'a' and 'b' can be any type at all, as long as 
they satisfy the constraints. For instance, for (^), the base can be of 
any numeric type, but the exponent must be integral, and the result is 
of the same numeric type as the base. So when you're actually using the 
function, you might be using it under any of the following types:

(^) :: Integer -> Integer -> Integer
(^) :: Float -> Integer -> Float
(^) :: Double -> Int8 -> Double

As you can see, the functions are already overloaded, in a sense. What 
Haskell does not support is 'overloading the implementation' the way 
derivatives of C++ (or whatever language first came up with this) do: a 
function cannot have different implementations for different types. 
Instead, a type class defines certain methods that each type that is an 
instance of it must implement. For example, (^) could be defined in 
terms of (==), (*), and (-), like so:

base ^ pow = if pow == 0 then 1 else base * (base ^ (pow-1))

(*) and (-) are methods of the Num class, and (==) belongs to a 
superclass of Num, so we can infer the type of this as:

(^) :: (Num a, Num b) => a -> b -> a

(The standard-library one restricts b to Integral, because this kind of 
definition is obviously valid only for integer exponents.)

We now have a generic implementation of (^) that works for any two 
number types. What we can't do is say that it should do something 
different for certain types: its definition shows that it depends only 
on the methods (==), (*), and (-), so if we want to change the behaviour 
of (^) we can do so only by changing their behaviour. This is doable 
only by changing the Num instance involved, which can only be done by 
changing the types in question.

The only things that can change their behaviour directly depending on 
the types involved are class methods, which are defined separately for 
each type. For instance, (-) is defined for Integers as bignum 
subtraction and (-) for Floats is some kind of built-in operation which 
eventually compiles to an fsub on x86. In fact, (**) is a method of the 
Floating class, and thus has a separate implementation for all 
floating-point types.

-- 
E-mail address: matti.niemenmaa+news, domain is iki (DOT) fi



More information about the Digitalmars-d mailing list