Opened 3 years ago

Last modified 3 years ago

#5316 new bug

Orphan instances strike again: ghc rejects a program at first but will accept it if you repeat the same compilation command

Reported by: jcpetruzza Owned by:
Priority: low Milestone:
Component: Compiler Version: 7.0.4
Keywords: Cc: conal@…
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: GHC rejects valid program Difficulty:
Test Case: Blocked By:
Blocking: Related Tickets:

Description

Consider these two modules (boiled down example from the checkers package):

{-# LANGUAGE ScopedTypeVariables, FlexibleContexts #-}

module T1

where

import Test.QuickCheck
import Text.Show.Functions ()


f :: forall m a b.  ( Arbitrary (a->b) ) => m (a,b) -> Property
f = const $ property (undefined :: (a->b) -> Bool)
module T2  where

import Control.Concurrent

g = threadDelay maxBound

I see the following interaction:

$ rm *hi *.o
$ ghc --make -c -O  T1 T2
[1 of 2] Compiling T2               ( T2.hs, T2.o )
[2 of 2] Compiling T1               ( T1.hs, T1.o )

T1.hs:12:13:
    Overlapping instances for Show (a -> b)
      arising from a use of `property'
    Matching instances:
      instance Show (a -> b) -- Defined in Text.Show.Functions
      instance Show base:System.Event.Manager.IOCallback
        -- Defined in base:System.Event.Manager
    (The choice depends on the instantiation of `a, b'
     To pick the first instance above, use -XIncoherentInstances
     when compiling the other instance declarations)
    In the second argument of `($)', namely
      `property (undefined :: (a -> b) -> Bool)'
    In the expression: const $ property (undefined :: (a -> b) -> Bool)
    In an equation for `f':
        f = const $ property (undefined :: (a -> b) -> Bool)
$ ghc --make -c -O  T1 T2
[2 of 2] Compiling T1               ( T1.hs, T1.o )
$ ghc --make -c -O  T1 T2
$ ls
T1.hi T1.hs T1.o  T2.hi T2.hs T2.o

I see this consistent behaviour in versions 7.0.{1,2,3,4} but not with 6.12.1

Change History (4)

comment:1 Changed 3 years ago by igloo

  • Resolution set to duplicate
  • Status changed from new to closed

Thanks for the report.

The overlapping instance in System.Event.Manager has been removed in the HEAD and 7.2, so this particular conflict won't arise again.

The poor handling of instances in different modules is a known problem: see #2356.

comment:2 Changed 3 years ago by jcpetruzza

  • Resolution duplicate deleted
  • Status changed from closed to new

My apologies for reopening the ticket, but after going through #2356 I can't see the duplication. What I am reporting here is that:

1) GHC will reject these modules only if compiled with optimizations on, as witnessed by:

$ rm *hi *o
$ ghc --make -c  T1 T2
[1 of 2] Compiling T2               ( T2.hs, T2.o )
[2 of 2] Compiling T1               ( T1.hs, T1.o )
$

2) GHC behaves differently if part of the program was already compiled.

Moreover, #2356 is about a design decision that makes it accept more programs than the Report mandates, not less.

Since as of #2356 GHC accepts duplicated instances as long as they don't conflict, I suspect that the real bug here is that the instance defined in System.Event.Manager shouldn't be in scope at all when compiling T1 (the first time).

Am I missing something?

comment:3 Changed 3 years ago by conal

  • Cc conal@… added

comment:4 Changed 3 years ago by simonpj

  • Milestone set to _|_
  • Priority changed from normal to low
  • Summary changed from ghc rejects a program at first but will accept it if one insists to Orphan instances strike again: ghc rejects a program at first but will accept it if you repeat the same compilation command

I believe I know what is happening here. Background:

  • GHC maintains a session-global "database" of instances from imported packages
  • This database only grows; when GHC needs to use functions from a module, it loads up that module and adds its instances to the database.
  • When looking up an instance, GHC looks up in this database (and in a similar data base for the package being compiled, but that's not an issue in this example).

In this case:

  • When you first say ghc --make T1 T2, GHC has to compile both modules.
  • T1 depends on Text.Show.Functions, which has a Show instance for (->)
  • T2 depends on System.Event.Manager, which also has a Show instance for (->).
  • So when it comes to T2, the second module is loaded, and the overlap is reported.
  • Without optimisation, System.Event.Manager isn't loaded at all, so its instances don't conflict.
  • When recompiling GHC starts with an empty database and, since T1 hasn't changed, it doesn't recompile it. So the database isn't as populated as before, and there's no overlap.

It's not very cool, but that's why.

Orphan instaces are a major pain. It's hard to do the Right Thing without being very inefficient, and I'm reluctant to penalise all compilations. It's a kind of bizarre case, and I'm not sure it's worth a lot of effort. So I'll make it low priority -- but if someone is finding it painful, please yell.

Note: See TracTickets for help on using tickets.