Reputation: 65
I'm a Haskell newbie currently learning about expressions and types. I recently learned that it's possible to use class constraints when specifying types in Haskell. To experiment with this, I tried the following code in GHCi:
ghci> a = 3 :: Num a => a
ghci> :t a
a :: Num a => a
ghci> :i Num
type Num :: * -> Constraint
class Num a where
(+) :: a -> a -> a
(-) :: a -> a -> a
(*) :: a -> a -> a
negate :: a -> a
abs :: a -> a
signum :: a -> a
fromInteger :: Integer -> a
{-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
-- Defined in ‘GHC.Num’
instance Num Double -- Defined in ‘GHC.Float’
instance Num Float -- Defined in ‘GHC.Float’
instance Num Int -- Defined in ‘GHC.Num’
instance Num Integer -- Defined in ‘GHC.Num’
instance Num Word -- Defined in ‘GHC.Num’
ghci> :t (==)
(==) :: Eq a => a -> a -> Bool
ghci> a == 5
False
I don’t understand why the expression a == 5
works without any errors. The variable a has the type Num a => a
, and since the Num
class does not define the (==)
function, I expected a type error.
Could someone explain why this works?
Upvotes: 1
Views: 55
Reputation: 116174
You raise a good point! Indeed, it can be argued that your code should not type check. And, by the same argument, 3 == 5
should also fail type checking, since 3
and 5
could be of a type that does not support ==
.
However... this is really harsh. Surely most programmers would expect 3 == 5
to be OK. Even if, as you mentioned, there would be valid theoretical reasons for that to fail.
The Haskell designers chose a compromise, adding a special case to the type system. For some "special" type classes like Num
, instead of reporting an error saying "your code does not tell me what specific numeric type a
to use, and this causes issues elsewhere", the compiler tries some default numeric types e.g. a ~ Integer
and moves on.
From an elegance point of view, this defaulting mechanism is a little wart. Worse, the exact type defaulting rules are not the same in GHCi and in GHC! You will find that at the GHCi prompt there is more magic defaulting going on so to avoid stressing the user too much, while GHC is not so lenient (unless you ask it to be).
However, from a pragmatic point of view, it makes some sense. No one really wants print 42
to fail.
Upvotes: 1
Reputation: 477462
ghci
will ad-hoc try to assign a type to a
. If you write:
a = 3 :: Num a => a
You actually have written something like:
a = fromInteger 3
which thus can be specialized to an Integer
, Double
, etc.
Now in the context of a == 5
, it thus requires a
to be (Num a, Eq a) => a
.
It will perform type defaulting and in thus case use Integer
. It thus checks for:
fromInteger 3 == fromInteger 5
and specializes the two to Integer
and then runs the (==)
function on these.
Types in Haskell are a bit "opposite" to what those are in for example Java. If in Java you write:
MyInterface foo;
It means that we only know foo
is an instance of MyInterface
.
Whereas in Haskell if we write:
x :: Num a => a
It means that x
is of a type a
for which Num a
holds. If we later use x
in another context, it can require extra type constraints.
If we thus write a function:
eq5 :: (Num a, Eq a) => a -> Bool
eq5 x = x == 5
Haskell thus promises that it will work for all types a
for which Num a
and Eq a
are satisfied.
Upvotes: 2