Skip to content

inconsistent semantics of type class instance visibility outside recursive modules

When you have an instance defined in a module that's part of a recursive module loop, when should modules outside the loop see it? Right now, GHC behaves differently depending on whether it's in batch or in single-shot compilation mode. Which one is correct? Dunno!

Here's the example (code at the bottom). The gist is that you have two modules A and B that define data types T and U, respectively, which depend on each other, so A and B are recursive modules and we need a boot file -- say, for A.

Now suppose we define an instance Eq T in the implementation of A but not in the boot file. B imports the boot file, so it doesn't know about this instance. And then I have some third module, Main, outside the loop, which imports only B. And now the central question: Does Main know about the Eq T defined in A?

We can test how GHC answers this question by defining an (orphan) instance for Eq T in Main. In batch compilation mode, Main is rejected for defining a duplicate instance. In single-shot compilation mode, however, Main is accepted and any equality test in Main uses the locally defined instance; i.e., B doesn't know about A's Eq T and so neither does Main.

So which is correct? If you ask me, the latter semantics is correct, but I can see why the former might be argued as well (e.g., according to the fixed-point semantics of import/export described in (1)). In any case, the semantics between the two compilation modes should probably agree, right?

-- A.hs-boot
module A where
  data T -- a mutually recursive data type, along with B.U
  t :: T -- some value to test for Eq in Main


-- B.hs
module B(module A, module B) where
  -- export A.{T,t} since Main doesn't import A
  
  import {-# SOURCE #-} A

  -- mutually recursive data type across modules
  data U = U | UT T


-- A.hs
module A where
  import B

  -- mutually recursive data type across modules
  data T = T | TU U

  -- the true instance
  instance Eq T where
    _ == _ = True

  -- some value to test Eq instance in Main
  t :: T
  t = T


-- Main.hs
module Main where
  import B -- no import of A

  -- an orphan instance for Eq T.
  -- okay in one-shot mode; not okay in batch (--make) mode
  instance Eq T where
    _ == _ = False

  -- in one-shot mode, this prints False
  main = putStrLn $ show $ t == t

Commands and output for batch mode:

$ ghc --make Main
[1 of 4] Compiling A[boot]          ( A.hs-boot, A.o-boot )
[2 of 4] Compiling B                ( B.hs, B.o )
[3 of 4] Compiling A                ( A.hs, A.o )
[4 of 4] Compiling Main             ( Main.hs, Main.o )

A.hs:8:10:
    Duplicate instance declarations:
      instance Eq T -- Defined at A.hs:8:10
      instance Eq T -- Defined at Main.hs:7:10

Commands and output for single-shot mode:

$ ghc -c A.hs-boot
$ ghc -c B.hs
$ ghc -c A.hs
$ ghc -c Main.hs
$ ghc A.o B.o Main.o -o main
$ ./main
False

Tested on GHC 7.6.3, 7.8.3, and 7.10.1.

(1) A Formal Specification for the Haskell 98 Module System. Iavor S. Diatchki, Mark P. Jones, and Thomas Hallgren. Haskell '02. http://web.cecs.pdx.edu/~mpj/pubs/hsmods.pdf

Trac metadata
Trac field Value
Version 7.10.1
Type Bug
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component Compiler
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system
Architecture
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information