TheMP
TheMP

Reputation: 8427

Understanding function types

I am getting a bit confused in my attempts to understand how Haskell determines function types. Here is an example:

boolFcn x y = x/=3 && y/=4

When I check the type of the above function, it gives me result:

(Num a1, Num a, Eq a1, Eq a) => a -> a1 -> Bool

Here is another example:

triangles = [ (a,b,c) | c <- [1..10], b <- [1..10], a <- [1..10] ]   

And apllying :t on triangles results with:

(Num t2, Num t1, Num t, Enum t2, Enum t1, Enum t) =>  [(t, t1, t2)]

A few questions arised in my head and I have serious trouble solving them by myself:

  1. Why does the type of boolFcn consists of a, a1 literals whilst the type of triangles consists of t,t1 literals? Is there any difference between a and t?
  2. Why does this the type of boolFcn cannot be simplified to:

    (Num a, Eq a) => a -> a -> Bool

a and a1 have the same typeclasses, so why can't I just simply write them using one a? When I check type of the function:

let sumthis x y = if x > y then x+y else x-y

I get a result:

(Ord a, Num a) => a -> a -> a

Why doesn't it result in:

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

I'm sorry if the question is trivial, though I would gladly hear any explanations/hints to this problem.

Upvotes: 4

Views: 145

Answers (2)

Benjamin Kovach
Benjamin Kovach

Reputation: 3260

First off, the variable names in types don't matter. They are arbitrarily named, but things of a given type will be the same. For example, a -> b -> b =/= a -> b -> a, but a -> b == c -> d (as long as they don't show up in the same type signature!). The typechecker gives polymorphically valued names to type variables on the fly, so don't worry about the minor differences in naming schemes: they don't matter. They're just variables.

As for how the typechecker actually determines a type of an expression, let's look at your first example:

(Num a1, Num a, Eq a1, Eq a) => a -> a1 -> Bool
boolFcn x y = x/=3 && y/=4

First, the typechecker sees x /= 3. This means that x must be an Eq, since (/=) is a part of the Eq typeclass, and, further, it must be a Num, because 3 gets interpreted as a polymorphic Num type during compilation. The same thing happens with y /= 4, but y and x don't interact apart from the usage of (&&). What this says is that x and y don't need to be the same type, hence the different type variables in the header. Finally, (&&) returns a Bool, so that's what the function returns.

Your second example is a tad trickier because of some syntactic sugar.

(Num t2, Num t1, Num t, Enum t2, Enum t1, Enum t) =>  [(t, t1, t2)]
triangles = [ (a,b,c) | c <- [1..10], b <- [1..10], a <- [1..10] ]   

Basically, when you do anything like xs = [a..b], the type of xs has to be Enum a => [a]. Intuitively, it's impossible to have a range of things that can't be enumerated over in order. Next, the numbers are again interpreted as polymorphic Num types. But why do we have three different type variables in the signature, each having to be both a Num and an Enum? The Num types don't actually have to be the same, because the tuple (a, b, c) is allowed to have different types of data in its first, second, and third spots. So each of those [1..10]s can be interpreted as lists of different enumerable numbers (i.e. (Int, Integer, Int) or (Integer, Integer, Int), and everything works out fine. If you were to collect a list instead of a tuple, like this:

triangles = [ [a,b,c] | c <- [1..10], b <- [1..10], a <- [1..10] ]   

a, b, and c are now required to have the same type, so the signature changes to triangles :: (Num t, Enum t) => [[t]].

Upvotes: 4

Jeff Burka
Jeff Burka

Reputation: 2571

  1. Yes, a and t are essentially the same in these examples. The difference is just a side effect of the type inference algorithms.

  2. Well, in boolFcn, (Num a, Eq a) => a -> a -> Bool would not be general enough, because the first two arguments need not be the same type. Consider the following call:

    boolFcn (4::Int) (4::Double)

    This is valid, because Int and Double are both members of the Num and Eq typeclasses, but they are clearly not the same type. In your sumthis example, x and y must be the same type because they are used as inputs to functions that require arguments of the same type.

    We can see that by checking :t (+), which returns (+) :: Num a => a -> a -> a. Since the parameters of + must be the same type, x and y must be the same type, so sumthis must require arguments of the same type. Therefore

    sumthis (4::Int) (4::Double)

    would not be valid.

Upvotes: 6

Related Questions