Reputation: 8427
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:
a, a1
literals whilst the type of triangles consists of t,t1
literals? Is there any difference between a
and t
?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
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
Reputation: 2571
Yes, a
and t
are essentially the same in these examples. The difference is just a side effect of the type inference algorithms.
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