Opened 9 years ago

Last modified 3 years ago

#2356 new bug

GHC accepts multiple instances for the same type in different modules

Reported by: claus Owned by:
Priority: low Milestone:
Component: Compiler Version: 6.8.3
Keywords: Cc: lennart@…, chak@…, mjm2002@…, leather@…
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: None/Unknown Test Case:
Blocked By: Blocking:
Related Tickets: #5316 Differential Rev(s):
Wiki Page:

Description

as mentioned by Simon PJ in this thread:

http://www.haskell.org/pipermail/haskell/2008-June/020436.html

here is the example, spelled out:

module B0 where
class C a where c :: a -> String
data T = T deriving Show

module B1 where
import B0
instance C T where c _ = "B1"
b = c T

module B2 where
import B0
instance C T where c _ = "B2"
b = c T

module Main where
import B1
import B2
main = print (B1.b,B2.b)

this is accepted without flags or errors and prints ("B1","B2").

the language report, section 4.3.2 clearly states:

A type may not be declared as an instance of a particular class more than once in the program.

Change History (16)

comment:1 Changed 9 years ago by guest

Cc: lennart@… added

This really surprises me. Why doesn't think produce a link error? Each instance declaration should give rise to a global symbol uniquely determined by the class and the type, I'd presume. And when they meet there will be a clash. Obviously I'm wrong, as this (scary) example shows.

comment:2 Changed 9 years ago by igloo

difficulty: Unknown
Milestone: 6.10.1
Priority: normalhigh

comment:3 in reply to:  1 Changed 9 years ago by chak

Cc: chak@… added

Replying to guest:

This really surprises me. Why doesn't think produce a link error? Each instance declaration should give rise to a global symbol uniquely determined by the class and the type, I'd presume. And when they meet there will be a clash. Obviously I'm wrong, as this (scary) example shows.

I believe, toplevel symbols binding dictionaries include the module (and package name) like all other toplevel symbols.

The example -although violating the current language definition- is less scary than one might think. You cannot abuse it (even if you chuck FDs into the mix) to coerce types unsafely (or circumvent strong typing in any other way).

comment:4 Changed 9 years ago by simonpj

Milestone: 6.10.1_|_
Priority: highlow

Currently this is by design. What would the unique global symbol be for

  instance C (T a) [Maybe b]

Oh, and T might be a type synonym.

We could encode the type as a string, I suppose, but the error message (from the linker) would still be execrable.

For indexed-type instances, overlap is unsound, so there's an eager check.

On reflection, if you don't use -fallow-overlapping-instances GHC should arguably do the same eager check for class instances; that would more faithfully implement the Report. Even doing that is not trivial, because GHC goes to some lengths to avoid looking at (literally) thousands of interface files on every compilation, just on the off-chance that there'll be an instance declaration lurking there.

With overlap allowed, it's clear that you can make different choices in different parts of the program.

So I'm not sure what to do; and the only thing that comes to mind isn't cheap. For now I'll put it as low-prio, unless people start yelling that it's ruining their day.

Simon

comment:5 Changed 9 years ago by morrow

Cc: mjm2002@… added

comment:6 Changed 9 years ago by guest

Oh, come on. It's easy to generate the unique string for the instance by flattening the type. HBC did this 15 years ago. :) The linker error is a little obscure, but since ghc starts the linker it can filter the error messages and print a better one. (HBC didn't do that.)

comment:7 Changed 9 years ago by spl

Cc: leather@… added
Version: 6.8.26.8.3

I prefer having GHC work as it does now. It makes sense to me that instances only overlap when used and not just when imported. If GHC changes, I would like the option to get back to the current approach, but without requiring -fallow-overlapping-instances. Perhaps some language extension. Also, has this been mentioned in haskell-prime?

I just recently learned about this issue first-hand. See the glasgow-haskell-users thread starting here with my own simple example that highlights the usefulness of instances working the way they do now.

comment:8 Changed 9 years ago by igloo

Here is an example of Data.Set going wrong due to multiple instances:

module Type where

data Foo = F1 | F2 | F3
    deriving (Eq, Show)
module Create where

import Data.Set
import Type

instance Ord Foo where
    x `compare` y = show x `compare` show y

s :: Set Foo 
s = fromList [F1, F2, F3]
module Consume where

import Data.Set
import Type

instance Ord Foo where
    x `compare` y = show y `compare` show x

f :: Set Foo -> Bool
f x = F1 `member` x
import Create 
import Consume

main :: IO ()
main = print (f s)
$ ./Main
False

comment:9 Changed 9 years ago by igloo

Re:

With overlap allowed, it's clear that you can make different choices in different parts of the program.

I don't agree. While I can see that you would want to commit to an instance early for optimisation reasons, I think you should then fail when linking if it turns out that you would have made a different choice had all of the program's instances been available.

comment:10 Changed 9 years ago by claus

some comments in this thread:

instance visibility (was: Re: The base library and GHC 6.10)

http://www.haskell.org/pipermail/libraries/2008-September/010717.html

comment:11 in reply to:  8 Changed 9 years ago by claus

Replying to igloo:

Here is an example of Data.Set going wrong due to multiple instances:

That depends on what you define as wrong, doesn't it?-)

In the Haskell'98 view, there is at most one instance for a class/type, so if you give such an instance, you may conclude that it is the only one, so the behaviour is wrong.

In the Ghc view, local instances are supported unless they actually conflict in use, so you may not conclude that the instance you see is the only one there'll ever be, so the behaviour is not wrong per se, just one of many possible behaviours.

In particular, you explicitly force Ghc to commit to the local instances leaving it no room for using other instances or reporting conflicts. If the functions where memberBy and fromListBy, with explicit comparison parameters, you're supplying the comparisons at the point of definition for f and s, instead of allowing them to be supplied at the point of use (where a conflict between differing comparisons might be noted).

If, instead you ask Ghc to leave the instances to use open, the behaviour will be closer to what you expected:

s :: Set Foo 
f :: Set Foo -> Bool

would become (needs {-# LANGUAGE FlexibleContexts #-})

s :: Ord Foo => Set Foo 
f :: Ord Foo => Set Foo -> Bool

comment:12 Changed 9 years ago by simonmar

Architecture: UnknownUnknown/Multiple

comment:13 Changed 9 years ago by simonmar

Operating System: UnknownUnknown/Multiple

comment:14 Changed 6 years ago by igloo

Type of failure: None/Unknown

See #5316 for a related issue.

comment:15 Changed 4 years ago by morabbin

comment:16 Changed 3 years ago by rwbarton

You can get this kind of instance incoherence without orphan instances or any extensions beyond FlexibleInstances. See https://gist.github.com/rwbarton/dd8e51dce2a262d17a80.

Note: See TracTickets for help on using tickets.