[GHC] #12201: Wrong instance selection with overlapping instance in a superclass

GHC ghc-devs at haskell.org
Sat Jun 18 11:02:37 UTC 2016


#12201: Wrong instance selection with overlapping instance in a superclass
-------------------------------------+-------------------------------------
        Reporter:  kanetw            |                Owner:
            Type:  bug               |               Status:  new
        Priority:  normal            |            Milestone:
       Component:  Compiler (Type    |              Version:  8.0.1
  checker)                           |
      Resolution:                    |             Keywords:
Operating System:  Unknown/Multiple  |         Architecture:
                                     |  Unknown/Multiple
 Type of failure:  None/Unknown      |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by simonpj):

 This is a delicate point about overlapping instances, which has
 never really been discussed.  Here's what is happening.

 In `test_bar` we get one constraint so solve: `B (Foo s Bool)`.
 And there is an instance declaration that solves
 it
 {{{
 instance B (Foo s a)
 }}}
 So GHC 8 just solves it, and I think it is right to do so.  So we
 get
 {{{
 test_bar :: s -> Int
 }}}

 Why does GHC 7 behave differently?  GHC 7 used a tricky mechanism
 called "silent superclasses" to solve a tricky problem to do with
 recursive instances.  As a result, GHC 7 effectively changed the
 instance for `B (Foo s a)` to
 {{{
 instance A (Foo s a) => B (Foo s a)
 }}}
 Now when solving `B (Foo s Bool)` GHC 7 finds it needs `A (Foo s Bool)`,
 and can't solve that (because of the overlap in A), so it just abstract,
 giving
 {{{
 test_bar :: A (Foo s Bool) => s -> Int
 }}}

 So the mysterious thing is really why GHC 8 accepts the instance
 declaration
 {{{
 instance B (Foo s a)
 }}}
 After all, that instance must also solve `A (Foo s a)`, and that involves
 overlap.  And that behaviour is the result of a second tricky issue.
 Suppose class `A` had a class method, and `Foo` had a data constructor,
 looking like this:
 {{{
 data Foo s a = FNil | FCons a (Foo s a)

 class A a where
   op :: a -> Int

 instance A (Foo s a) where
   op FNil = 0
   op (FCons _ f) = op f   -- Needs A (Foo s a)!

 instance {-# OVERLAPPING #-} A (Foo Int Bool) where
   ...
 }}}

 When typechecking the commented line for `op`, we need to solve `A (Foo s
 a)`.
 And on this occasion it would be Bad to fail to solve it,
 saying "instance overlap".
 See `Note [Subtle interaction of recursion and overlap]` in `TcInstDcls`.
 So there is special magic to allow this to work.  (For the afficionados,
 we use `SkolemTv True` rather than `SkolemTv False` in the
 `TcTyVarDetails`.)

 Alas, this magic also applies to the superclass solving, but I now think
 that it should not do so.  Removing the magic would make the instance
 declaration be rejected; instead you would have to delay the superclass
 choice by writing
 {{{
 instance A (Foo s a) => B (Foo s a)
 }}}
 I think this is probably the right thing to do. Do others agree?

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/12201#comment:5>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list