I know this is code is a bit silly, but can someone explain why this isList [42]
returns True
whereas isList2 [42]
prints False
It's really quite simple. Let's ask GHCi what the type of isList2
is:
∀x. x ⊢ :t isList2
isList2 :: a -> Bool
This doesn't match the [a]
instance (even though it could, via unification), but it does match the a
instance immediately. Therefore, GHC selects the a
instance, so isList2
returns False
.
This behavior is precisely what IncoherentInstances
means. Actually, this is a rather nice demonstration of it.
Hilariously, if you simply disable IncoherentInstances
, we get exactly the opposite effect, and GHCi now says this:
∀x. x ⊢ :t isList2
isList2 :: [Integer] -> Bool
This happens because isList2
is a top-level binding not defined using function syntax, and thus subject to the Dreaded Monomorphism Restriction. So it gets specialized to the instance it's actually used with.
Adding NoMonomorphismRestriction
as well as disabling IncoherentInstances
, we get this instead:
∀x. x ⊢ :t isList2
isList2 :: IsList a => a -> Bool
∀x. x ⊢ isList2 'a'
False
∀x. x ⊢ isList2 "a"
True
∀x. x ⊢ isList2 undefined
:19:1:
Overlapping instances for IsList a0 arising from a use of `isList2'
Which is the expected overlapping behavior, with the instance chosen based on use and complaints if the choice is ambiguous.
Regarding the edit to the question, I don't believe the desired result is possible without type annotations.
The first option is to give isList2
a type signature, which prevents IncoherentInstances
from selecting an instance too early.
isList2 :: (IsList a) => a -> Bool
isList2 = isList
You'll probably need to do the same anywhere else isList
is mentioned (even indirectly) without being applied to an argument.
The second option is to disambiguate the numeric literals and disable IncoherentInstances
.
main =
print (isList (42 :: Integer)) >>
print (isList2 (42 :: Integer)) >>
print (isList [42]) >>
print (isList2 [42])
In this case, there's enough information to pick a most-specific instance, so OverlappingInstances
does its thing.