I've been experimenting with this simple implementation of HLists and a function hasInt
which returns True
if an Int
is a member of the list:
{-# LANGUAGE FlexibleInstances #-}
data HNil = HNil
deriving (Show, Read)
data HCons a b = HCons a b
deriving (Show, Read)
class HasInt a where
hasInt :: a -> Bool
instance HasInt HNil where
hasInt _ = False
instance HasInt as => HasInt (HCons a as) where
hasInt (HCons a as) = (isInt a) || (hasInt as)
class IsInt a where
isInt :: a -> Bool
instance IsInt Int where
isInt _ = True
instance {-# OVERLAPPABLE #-} IsInt a where
isInt _ = False
three = 3 :: Int
main = do
putStrLn $ "isInt three = " ++ show (isInt three) -- True
putStrLn $ "isInt True = " ++ show (isInt True) -- False
print $ hasInt $ HCons three $ HCons True HNil -- False ???
This doesn't give the expected results. However, it does seem to work if I change:
instance HasInt as => HasInt (HCons a as) where
to:
instance (IsInt a, HasInt as) => HasInt (HCons a as) where
On the other hand, I normally expect GHC to complain if I use a type class function but don't include the constraint, and I don't get any such indication in this case.
Clearly it has to do something with the catch-all instance IsInt a
. I will get the Could not deduce (IsInt a) arising from a use of 'isInt'
error if I replace the catch-all instance with:
instance IsInt Bool where isInt _ = False
instance IsInt HNil where isInt _ = False
My question is: Is this expected behavior of GHC - that it will silently use a catch-all instance if there is no explicit type class constraint?
Yes, this is the expected behavior. If you write
instance Foo a
you are declaring that all types are instances of Foo
, and GHC believes you.
This is 100% analogous to the following:
foo :: Int -> Bool
foo x = x > 0
Even though you don't have Ord Int
in the context, GHC knows there is such an instance. Likewise, in:
bar :: a -> b
bar x = {- use the Foo instance for x here -}
Even though you don't have Foo a
in the context, GHC knows there is such an instance.