3

Consider the following program, which only compiles with incoherent instances enabled:

{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, FlexibleInstances #-}
{-# LANGUAGE IncoherentInstances #-}

main = do
  print (g (undefined :: Int))
  print (g (undefined :: Bool))
  print (g (undefined :: Char))

data True

class CA t where
  type A t; type A t = True
  fa :: t -> String
instance CA Int where
  fa _ = "Int"

class CB t where
  type B t; type B t = True
  fb :: t -> String
instance CB Bool where
  fb _ = "Bool"

class CC t where
  type C t; type C t = True
  fc :: t -> String
instance CC Char where
  fc _ = "Char"

class CAll t t1 t2 t3 where
  g :: (t1 ~ A t, t2 ~ B t, t3 ~ C t) => t -> String

instance (CA t) => CAll t True  t2   t3  where g = fa
instance (CB t) => CAll t  t1  True  t3  where g = fb
instance (CC t) => CAll t  t1   t2  True where g = fc

When compiling without incoherent instances it claims multiple instances match. This would seem to imply that when incoherent instances are allowed, the instances would be chosen arbitrarily.

Unless I'm exceedingly lucky, this would result in compile errors as the instance constraints in most cases would not be satisfied.

But with incoherent instances I get no compile errors, and indeed get the following output with the "correct" instances chosen:

"Int"
"Bool"
"Char"

So I can only conclude either one of a few things here:

  1. GHC is backtracking on instance context failures (something it says it doesn't do in it's own documentation)
  2. GHC actually knows there's only one instance that matches, but isn't brave enough to use it unless one turns on incoherent instances
  3. I've just got exceedingly lucky (1 in 33 = 27 chance)
  4. Something else is happening.

I suspect the answer is 4 (maybe combined with 2). I'd like any answer to explain what's going on here, and how much I can rely on this behavior with incoherent instances? If it's reliable it seems I could use this behaviour to make quite complex class hierarchies, that actually behave somewhat like subtyping, e.g. I could say all types of class A and class B are in class C, and an instance writer could make an instance for A without having to explicitly make an instance for C.

Edit:

I suspect the answer has something to do with this in the GHC docs:

  1. Find all instances I that match the target constraint; that is, the target constraint is a substitution instance of I. These instance declarations are the candidates.
  2. Eliminate any candidate IX for which both of the following hold:
    • There is another candidate IY that is strictly more specific; that is, IY is a substitution instance of IX but not vice versa.
    • Either IX is overlappable, or IY is overlapping. (This "either/or" design, rather than a "both/and" design, allow a client to deliberately override an instance from a library, without requiring a change to the library.)
  3. If exactly one non-incoherent candidate remains, select it. If all remaining candidates are incoherent, select an arbitary one. Otherwise the search fails (i.e. when more than one surviving candidate is not incoherent).

  4. If the selected candidate (from the previous step) is incoherent, the search succeeds, returning that candidate.

  5. If not, find all instances that unify with the target constraint, but do not match it. Such non-candidate instances might match when the target constraint is further instantiated. If all of them are incoherent, the search succeeds, returning the selected candidate; if not, the search fails.

Correct me if I'm wrong, but whether incoherent instances is selected or not, in the first step, there's only one instance that "matches". In the incoherent case, we fall out with that "matched" case at step 4. But in the non-incoherent case, we then go to step 4, and even though only one instance "matches", we find other instances "unify". So we must reject.

Is this understanding correct?

And if so, could someone explain exactly what "match" and "unify" mean, and the difference between them?

leftaroundabout
  • 117,950
  • 5
  • 174
  • 319
Clinton
  • 22,361
  • 15
  • 67
  • 163
  • 1
    I don't think anything funny is happening - if you want to see the incoherence, add an instance for `CB Int`, etc. It will now pick one of those arbitrarily when solving `g (undefined :: Int)`. The reason that that it seems to "just work" is that it can't actually pick *any* of those `CAll` instances, because of the extra type variables in the head. But when you allow incoherence, it essentially says "do *any* of these instances work?" if it can't find one by regular inference (which it can *never* do here). In your case, you have it nicely set up so that only one instance will ever be valid. – user2407038 Feb 21 '16 at 13:50
  • 2
    To clarify, your point 1 is true - GHC does not backtrack on instance context failures, when doing *type constraint solving*. Picking an instance "arbitrarily" in the presence of incoherent instances is *not* constraint solving. It *does* consider the contexts - in fact it essentially just reduces every possible instantiation until it gets one that works and then it says - yep its this one - because it's arbitrary. Doesn't matter if there is another instantiation which also works. – user2407038 Feb 21 '16 at 13:55
  • 4
    Just ... don't. `IncoherentInstances` is a highly effective way to shoot yourself in the foot. – dfeuer Feb 21 '16 at 14:04
  • Thanks @user2407038. See my edit, is this a roughly correct understanding of what's happening? – Clinton Feb 21 '16 at 14:17
  • @Clinton I believe something completely different is happening. In step 1, *all* of the instances match! This is because `t` is completely free in all of them - and there is no relation as far as the typecheck is concerned between `t` and the rest of the type vars. Step 2 eliminates no instances. Step 3 does "select an arbitary one" - they are all incoherent. Step 4 returns this candidate - step 5 never happens! – user2407038 Feb 21 '16 at 14:53
  • How is it always selecting the right one at step 3 then? – Clinton Feb 21 '16 at 14:57
  • "Match" refers to the instance head matching algorithm. "Unify" is sort of borrowing a word from another part of typechecking. In general two *types* are said to unify if there is an instantiation which makes them equal. In this context, a *constraint* and a *instance head* are said to "unify" if there is an instantiation which makes them *match*. – user2407038 Feb 21 '16 at 14:58
  • Surely if step 1 and 2 eliminate no candidates than I wouldn't get the right one very often. – Clinton Feb 21 '16 at 14:59
  • Sorry... I must be wrong. Step 5 finds that all of the instances qualify as "might match when the target constraint is further instantiated.". When it does this further instantiation, it finds that only one matches (when it does this - the alg doesn't specify). That must be how it finds the correct instance... What I can't figure out, is how it even gets to step 5. There are certainly *no* coherent candidates at all, meaning step 4 must return. There must be some tricky edge cases not mentioned in the docs... might have to dig into to source code to find out what's going on. – user2407038 Feb 21 '16 at 15:11
  • I'm thinking somehow by step 4 we only have on instance. If incoherent instances is on, it's selected, but if incoherent instances is off, we then go to step 5 and get problems. – Clinton Feb 21 '16 at 15:21
  • I suspect the GHC docs are not quite precise about what's happening in the actual code; they were probably written before type families existed and not revisited after, even though changes to instance selection almost surely happened at that time. – Daniel Wagner Feb 21 '16 at 15:57

0 Answers0