Reputation: 1197
Since:
1
has the type Num a => a
1.0
has the type Fractional a => a
Why does 1 + 1.0
have the type Fractional a => a
?
This seems strange to me because 1
is not fractional. Only 1.0
is fractional. So, how did 1
turn into fractional and get combined with 1.0
to form a fractional?
Since only Num
has the +
operator, it would seem more natural to me if 1.0
turned into Num
, got combined with 1
to produce a final Num
, although that would be strange too, because we would lose information going from 1.0
to 1
.
Upvotes: 2
Views: 188
Reputation: 370357
Every Fractional is a Num
, but not every Num
is a Fractional
. So if we have a Num
like 1
, it could either be a Fractional
(because some Num
s are Fractional
s) or it could not be. However 1.0
can only be a Fractional
, it definitely can't be some other Num
like Integer
.
So when the compiler sees that you add 1 to a Fractional
, it realizes that 1
must be a Fractional
as well in this case - otherwise you'd not be allowed to add it to a Fractional
.
Here's a similar example that only involves user-defined type classes instead of Num
s. Maybe this makes things clearer for you:
class Foo a where
foo :: a
class Foo a => Bar a where
bar :: a
combine :: a -> a -> a
Through the above type classes we now have the following methods:
foo :: Foo a => a
bar :: Bar a => a
combine :: Bar a => a -> a -> a
So now let's try to combine foo
and bar
like this:
combine foo bar
This is roughly equivalent to you trying to add 1
(of type Num a => a
) and 1.0
(of type Fractional a => a
) in your example. And just like your example, this works fine and has the type Bar a => a
.
Upvotes: 10
Reputation: 116174
1
(which technically stands for fromInteger
applied to the Integer
value 1
) belongs to all the types in class Num
.Fractional
belong to class Num
as well. Ergo,
Number 1
belongs to all the types in class Fractional
.
Upvotes: 2
Reputation: 120731
Type classes are very much not like OO classes, this can't be overemphasized.
In particular, “if 1.0
turned into Num
” doesn't make any sense. Num
is a type class, not a type, so nothing can ever “turn into a Num
”. In fact, nothing ever turns into something else at all in Haskell – everything has a concrete type, that is fixed.
Now you ask, how do polymorphic functions work then? Well, it's called parametric polymorphism for a reason: what seems to be an “arbitrary type a
” is really a type parameter. Like a function parameter, these aren't variables in the sense that they can ever change their value after the fact, but they are variable in the sense that the caller of the function is allowed to choose any particular “type value” for a
– provided it fulfills the type-class constraint.
So in a sense, the literal 1
is a function: it accepts a type argument a
, and returns a value 1 :: a
. What it requires is that a
is in class Num
.
Then we have (+)
and 1.0
, both of which also need that same a
argument. (+)
again requires Num
, nothing new; but 1.0
requires Fractional a
. So all in all, 1 + 1.0
is a function that accepts “three copies of” the type argument a
, and requires
Num a
Num a
Fractional a
– which also requires Num a
because Num
is superclass of Fractional
.It would be pretty awkward if we actually had to write the type out as
(1 + 1.0) :: (Num a, Num a, Fractional a, Num a) => a
so it is allowed to leave out the redundant constraints, only leaving Fractional a
, which implies all the rest. What we can't do is only leave one of the Num
constraints, because that would not imply Fractional
.
Upvotes: 4