First off, as @dfeuer aptly points out, in "real-life" Haskell programming you wouldn't want to use typeclasses for this kind of stuff. The Haskell community generally wants typeclasses with a set of meaningful algebraic laws that govern the behavior of the typeclass instances. These laws are not checked by the compiler (although programmers will often write QuickCheck tests for them).
With that out of the way, I'm assuming the book is teaching you this purely for familiarizing you with the mechanics of typeclasses (that they can have constraints which reference the typeclass itself) and not espousing this typeclass as a particularly good one.
@duplode gives you the correct answer as to what to do in this scenario: annotate your types.
Main> tooMany (10 :: Int, 10 :: Int)
False
So why can't GHC automatically find the correct instance for (10, 10)
and instead requires type annotations? Well fundamentally it's because Haskell typeclasses are open, meaning that anyone can at any time make a new instance of a typeclass you've created in another module which you don't know about, e.g. if you put this typeclass in a library, downstream consumers of that library can make their own instances of this typeclass.
In this case, as you've already noticed, numeric literals don't actually have a monomorphic type in Haskell. 10
is of type Num a => a
, not Int
nor Integer
nor Double
nor anything else like that. Because typeclasses are open, even though you have only defined a single instance of a Num
type for TooMany
(namely Int
), the Haskell compiler cannot rely on that to infer that there is in fact only one instance of a Num
type for TooMany
and therefore that (10, 10)
must have type (Int, Int)
. Anyone else who uses your code could define their own instance of TooMany
for, say, Double
s.
In particular, a consumer of your module could make a new type with degenerate type instances for TooMany
and Num
.
-- Imagine you published `TooMany` as a library on Hackage
-- and now a downstream consumer writes the following
-- This example happens to break the laws for Num,
-- but the compiler doesn't know about typeclass laws
data DegenerateType = DegenerateType
instance Num DegenerateType where
_ + _ = DegenerateType
_ * _ = DegenerateType
negate _ = DegenerateType
abs _ = DegenerateType
signum _ = 0
-- The next line dictates what a numeric literal
-- actually means for a DegenerateType
fromInteger _ = DegenerateType
instance TooMany DegenerateType where
tooMany _ = True
Now tooMany (10, 10)
has different behavior depending on what monomorphic type 10
is.
Main> tooMany (10 :: DegenerateType, 10 :: DegenerateType)
True
Therefore tooMany (10, 10)
is not enough to specify the behavior of tooMany
and you must use type annotations.