Playing around with some code:
{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}
class Arity f where
arity :: f -> Int
instance Arity x where
arity _ = 0
instance Arity f => Arity ((->) a f) where
arity f = 1 + arity (f undefined)
Without IncoherentInstances
:
ghci> arity foldr
blah blah ambiguous blah blah possible fix blah
ghci> arity (foldr :: (a -> Int -> Int) -> Int -> [a] -> Int)
3
ghci> let f x y = 3 in arity f
2
ghci> arity $ \x y -> 3
2
If we add IncoherentInstances
to the list of pragmas, then it can handle foldr
without needing a monomorphic type signature, but it gets the wrong answer on lambdas:
ghci> arity foldr
3
ghci> let f x y = 3 in arity f
2
ghci> arity $ \x y -> 3 -- should be 2
0
What is the black magic behind Incoherent Instances? Why does it do what it does here?
Well this is quite complicated. Lets start with the ambiguous error:
Normally, without overlapping instances, when attempting to match a type against a class, it will compare the type against all instances for that class. If there is exactly one match, it will use that instance. Overwise you will either get a no instance error (eg with
show (*)
), or an overlapping instances error. For example, if you remove theOverlappingInstances
language feature from the above program, you will get this error witharity (&&)
:It matches
Arity (a -> f)
, asa
can beBool
andf
can beBool -> Bool
. It also matchesArity x
, asx
can beBool -> Bool -> Bool
.With
OverlappingInstances
, when faced with a situation when two or more instances can match, if there is a most specific one it will be chosen. An instanceX
is more specific than an instanceY
ifX
could matchY
, but not vice versa.In this case,
(a -> f)
matchesx
, butx
does not match(a -> f)
(eg considerx
beingInt
). SoArity (a -> f)
is more specific thanArity x
, so if both match the former will be chosen.Using these rules,
arity (&&)
will firstly matchArity ((->) a f)
, witha
beingBool
, andf
beingBool -> Bool
. The next match will havea
beingBool
andf
being bool. Finally it will end matchingArity x
, withx
being Bool.Note with the above function,
(&&)
result is a concrete typeBool
. What happens though, when the type is not concrete? For example, lets look at the result ofarity undefined
.undefined
has the typea
, so it isn't a concrete type:You get an abiguous type variable error, just like the one for foldr. Why does this happen? It is because depending on what
a
is, a different instance would be required. Ifa
wasInt
, then theArity x
instance should be matched. Ifa
wasInt -> Int
, then theArity ((->) a f)
instance should be matched. Due to this, ghc refuses to compile the program.If you note the type of foldr:
foldr :: forall a b. (a -> b -> b) -> b -> [a] -> b
, you will notice the same problem: the result is not a concrete variable.Here is where
IncoherentInstances
comes in: with that language feature enabled, it will ignore the above problem, and just choose an instance that will always match the variable. Eg witharity undefined
,Arity x
will always matcha
, so the result will be 0. A similar thing is done at forfoldr
.Now for the second problem, why does
arity $ \x y -> 3
return 0 whenIncoherentInstaces
is enabled?This is very weird behaviour. This following ghci session will show how weird it is:
This leads me to think that there is a bug in ghc, where
\a b -> 3
is seen byIncoherentInstances
to have the typex
instead ofa -> b -> Int
. I can't think of any reason why those two expressions should not be exactly the same.