Babra Cunningham
Babra Cunningham

Reputation: 2967

Automatically recognizing types when using typeclass instances?

I'm going over some basic typeclass implementations:

data Colour = Red | Green | Black

class BasicEquality a where
   isEqual :: a -> a -> Bool

instance BasicEquality Bool where
  isEqual True True    = True
  isEqual False True   = True
  isEqual True False   = True
  isEqual _ _          = False

instance BasicEquality Colour where
  isEqual Red Green    = True
  isEqual _ _          = False

instance BasicEquality Int where
  isEqual 100 100     = True
  isEqual _ _         = False

main = do 
  print $ isEqual Red Green //Output: True
  print $ isEqual 100 100 //Output: Error Ambiguous type variable ‘a0’ arising from a use of ‘isEqual’

Clearly, this works when I specify print $ isEqual (100 :: Int) (100 :: Int)

Why does Haskell implicitly recognise that Red and Green are Colours yet requires me to specifically bind 100 to Int?

Upvotes: 0

Views: 37

Answers (1)

leftaroundabout
leftaroundabout

Reputation: 120711

Red etc. are monomorphic values, i.e. they have a concrete type

Red :: Colour

So when Red turns up in your code, the compiler knows immediately what type it is, and it can use this information to infer what typeclass instance to use for e.g. BasicEquality.

OTOH, numeric literals like 100 have the polymorphic type Num a => a. The reason is that we want to be able to write

Prelude> replicate 3 'q'
"qqq"

as well as

Prelude> sqrt 3
1.7320508075688772

If 3 had simply the type Int, the latter would not work, because sqrt expects a type of e.g. Double. Since numerical literals are actually polymorphic, this doesn't matter.

The trouble in your case is that isEqual is also polymorphic. There are thus multiple different types the compiler could select. Image you also had

instance BasicEquality Integer where
  isEqual 50 1000     = True
  isEqual _ _         = False

then isEqual 100 100 could be interpreted as either isEqual (100 :: Int) 100 (which is True) or isEqual (100 :: Integer) 100 (which is false).

In practice, this is seldom so much of an issue, because you won't be comparing different numerical literals (these are already known, so you could as well simply hard-code the result!) but at most one literal with one variable in your program, and that variable will usually have already a type determined from the context. For instance,

*Main> let b = length "foobar"
*Main> isEqual 4 b
False

works without any signature.

Upvotes: 3

Related Questions