0

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?

ErikR
  • 51,541
  • 9
  • 73
  • 124
  • I don't see any `instance IsInt Int where isInt _ = True` here, which makes me very confused about how you get anything to return True ever. – amalloy Jul 25 '17 at 23:50
  • Yes - I missed that line. Question updated. – ErikR Jul 26 '17 at 00:04
  • This is one of the reasons overlapping instances are so horrible. If you leave out an instance, everything will compile and everything will be wrong. – dfeuer Jul 26 '17 at 00:54

1 Answers1

3

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.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • I guess I understand, but the surprising thing is the code behaves differently depending on whether or not the constraint is present or not, and I don't get any indication from GHC either in the form of an error or warning that I might be in for a surprise. – ErikR Jul 26 '17 at 01:30
  • 1
    @ErikR That's what you signed up for when you enabled `OverlappingInstances`, I'm afraid. – Daniel Wagner Jul 26 '17 at 01:53