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 |