
#10270: inconsistent semantics of type class instance visibility outside recursive modules -------------------------------------+------------------------------------- Reporter: skilpat | Owner: Type: bug | Status: new Priority: normal | Milestone: Component: Compiler | Version: 7.10.1 Keywords: | Operating System: MacOS X Architecture: x86_64 | Type of failure: GHC rejects (amd64) | valid program Test Case: | Blocked By: Blocking: | Related Tickets: Differential Revisions: | -------------------------------------+------------------------------------- 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? {{{#!hs -- 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 -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/10270 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler