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
you are declaring that all types are instances of
Foo, and GHC believes you.This is 100% analogous to the following:
Even though you don't have
Ord Intin the context, GHC knows there is such an instance. Likewise, in:Even though you don't have
Foo ain the context, GHC knows there is such an instance.