Haskell\'s type system is powerful and liked for its mathematical rigorousness and logical soundness, on the other side something as naive as below makes me wonder why it do
why can't
Int
be converted toNum
onx3
Int
cannot be converted to Num
because Int
is a type and Num
is a type class. The difference between these two kinds of entities will hopefully become clear in what follows.
Int
cannot be converted to anything else because Haskell has no conversions in the sense you are using here. There are no implicit casts. What does happen is a polymorphic type getting specialised to some definite type; however, a definite type never becomes something else automatically.
With that in mind, let's consider your examples.
Prelude> let x1 = 1
Prelude> :t x1
x1 :: Num a => a
x1
here is polymorphic, which means it can assume different types depending on how you use it. This indeterminacy can be recognised by the presence of the type variable a
(type variables, unlike concrete types, are not capitalised). The type of x1
, though polymorphic, is to some extent restricted by the constraint Num a
. Num a => a
can be read as "any type that has an instance of the type class Num
", while a plain a
would mean "any type whatsoever".
Prelude> let x2 = 1 :: Int
Prelude> :t x2
x2 :: Int
Introducing the type annotation :: Int
means requesting Int
to be unified with the type of 1
, Num a => a
. In this case, that simply means replacing the type variable a
with Int
. Given that Int
does have an instance of Num
, that is a valid move, and the type checker happily accepts it. The type annotation specialises the polymorphic type of 1
to Int
.
Prelude> let x3 = (1 :: Int) :: Num a => a
Couldn't match expected type ‘a1’ with actual type ‘Int’
The type of 1 :: Int
is Int
. The second annotation demands unifying it with Num a => a
. That, however, is impossible. Once a type is specialised, you can't "forget" the type and revert the specialisation just by providing a type annotation. Perhaps you are thinking of OOP upcasting; this is not at all the same thing. By the way, if the type checker accepted x3
, you would be able to write x4 = ((1 :: Int) :: Num a => a) :: Double
, thus converting an Int
to a Double
. In the general case, though, there is no way this conversion can happen like that, as you didn't tell how the conversion is to be done; as for the special cases, there aren't any. (Converting an Int
to a Double
is certainly possible, but it requires an appropriate function. For instance, you may find it relevant to consider how the type of fromIntegral
relates to what it does.)
Prelude> let f1 :: Num a => a -> a; f1 = id
Prelude> :t f1 (1 :: Int)
f1 (1 :: Int) :: Int
The principles here remain the same. The only difference is that you have to consider how the types of argument and result are related to each other. The type of id
is a -> a
. It specialises just fine to Num a => a -> a
. Passing an Int
argument further specialises it to Int -> Int
, and so you get a result of type Int
.
Prelude> let f2 :: Int -> Int; f2 = id
Prelude> :t f2 1
f2 1 :: Int
f1
had a polymorphic type that you specialised by feeding it an Int
argument, while f2
has a monomorphic type, and so there is no need to specialise it. id
is specialised from a -> a
directly to Int -> Int
, while 1
is specialised from Num a => a
to Int
because you are feeding it to a function that expects an Int
argument.
Prelude> let f3 :: Num a => a -> Int; f3 = id
Couldn't match type ‘a’ with ‘Int’
Here, you want to unify a -> a
, the type of id
, with Num a => a -> Int
. However, if you replace a
with, for instance, Double
in Num a => a -> Int
, you get Double -> Int
, which can't possibly unify with a -> a
, because it changes types while a -> a
doesn't. (That is the point of Thomas M. DuBuisson's comment above: the type of your implementations is not compatible with that of id
, because id
can't change the type of anything.)
Prelude> let f4 :: Num a => Int -> a; f4 = id
Couldn't match type ‘a’ with ‘Int’
Finally, this is just like f3
, except that the mismatch happens on the result type rather than on that of the argument. Putting a different spin on it this time, you can't implement a Num a => Int -> a
function by settling on a specific type with a Num
instance (be it Int
, Double
, etc.) and then "upcasting" it to Num a => a
, because there is no such thing as upcasting. Rather Num a => Int -> a
must work for any choice of a
whatsoever that has an instance of Num
.