13
votes

Consider the following Haskell function:

sign a
  | a < 0 = (-1)
  | a > 0 = 1
  | otherwise = 0

When I load this into ghci I expected :t sign to be:

sign :: (Num a, Ord a) => a -> Integer

Instead it inferred it as:

*Main> :t sign
sign :: (Num a1, Num a, Ord a1) => a1 -> a

Similarly, if I ask for the type of the integer 5, I expected Integer, but instead I got

*Main> :t 5
5 :: Num a => a

There's something I am not understanding about Haskell's types. The thing is, if all I know about the return type of sign is that it is an instance of the Num typeclass, then I should not be able to pass its return value into this function:

double :: Integer -> Integer
double x = x * 2

That is, my double function requires an Integer, not just any instance of Num.

Yet, the following works just fine:

*Main> double (sign 5.5)
2

What is it that I am mis-understanding about Haskell's type system?

3
By the way, there is, of course also the function signum :: Num a => a -> a, i.e you pass a number into it, and get back a number of the same type.mrueg
The fact that you passed it to double gives the type inference engine more knowledge--it knows it works with double, and figures out how to make that possible.jpaugh

3 Answers

19
votes

The thing is, if all I know about the return type of 'sign' is that it is an instance of the Num typeclass, then I should not be able to pass its return value into this function:

Right, if that were all that you knew, you couldn't pass it to double.

But the type

sign :: (Num a1, Num a, Ord a1) => a1 -> a

means that the result type of sign is whichever Num type the caller demands. Type variables in type signatures are (implicitly) universally quantified, not existentially, like for e.g. Java interfaces.

sign can produce a return value of arbitrary type, subject to the restriction it be an instance of Num, and the type it returns is determined by the calling context.

If the caller wants an Integer, it gets one. If it wants a Double, no problem either.

I forgot to mention initially:

Similarly, if I ask for the type of the integer 5, I expected "Integer", but instead I got

    *Main> :t 5
    5 :: Num a => a

Numeric literals are polymorphic, an integer literal stands for fromInteger value, and a fractional literal for fromRational value.

10
votes

I just wanted to clarify @DanielFischer's answer a little. A type signature like f :: Num b => a -> b means that f is capable of returning any instance of the typeclass Num. When f is called, Haskell uses the context (the type signature of the caller) to determine the concrete type of b.

Moreover, Haskell's numeric literals are an example of this type of polymorphism. That's why :t 5 gave you Num a => a. The symbol 5 is capable of acting as any type of number, not just an integer. The context it appears in determines which it will be.

7
votes

In Haskell, if a function returns type x is its result, that means that the caller can choose what x should be, not the function. Rather, the function must be able to return any possible type.

Your sign can return any type of data - including Integer. The double function wants an Integer, so that's just fine - sign can return that.

Another part of the puzzle you may not be aware of: In Java, 2 has type int and 2.0 has type double. But in Haskell, 2 has type Num x => x - in other words, any possible number type. (Also 2.0 has type Fractional x => x, which is a similar deal.)