Ticket #1220 (closed bug: invalid)

Opened 6 years ago

Last modified 5 years ago

Newtype deriving should only work if superclasses are newtype-derived

Reported by: simonpj Owned by:
Priority: normal Milestone: 6.8.2
Component: Compiler (Type checker) Version: 6.6
Keywords: Cc: Twan van Laarhoven [twanvl@…
Operating System: Unknown/Multiple Architecture: Unknown/Multiple
Type of failure: Difficulty: Unknown
Test Case: Blocked By:
Blocking: Related Tickets:

Description

Twan van Laarhoven writes: I just noticed some unexpected consequences of the way newtype deriving is implemented in GHC. Because the dictionary of the underlying type is reused, so are base classes. This message is a literate Haskell program illustrating the problem.

 > {-# OPTIONS_GHC -fglasgow-exts #-}

This problem comes up when an instance method calls a method of a base class. Consider the following useless example:

 > class Show a => Show2 a where
 >       show2 :: a -> String
 >              show2 = show
 >
 > instance Show2 Int

Now consider a type deriving Show2, but having a different implementation of Show (also derived).

 > newtype Meter = Meter Int
 >              deriving (Eq, Show, Show2)

Now, for show2 the derived Show instance (which also shows the constructor name) is *not* used, instead the Show Int instance is used:

] > show2 (Meter 1)
] "1"
] > show  (Meter 1)
] "Meter 1"

This is quite unexpected, unless you consider what is going on behind the scenes. Even more confusingly, GHC requires that there is a Show instance for Meters, even if it will not be used!

]    No instance for (Show Meter)
]      arising from the superclasses of an instance declaration at ...
]    Probable fix: add an instance declaration for (Show Meter)
]    In the instance declaration for `Show2 Meter'

This problem can come up whenever a class instance uses a function from a base class. Right now this not likely happen, but it will become more common if the standard classes are split up:

 > class Additive a where
 >       add :: a -> a -> a
 > class Additive a => Subtractive a where
 >       neg :: a -> a
 >       sub :: a -> a -> a
 >       sub x y = add x (neg y) -- calls base class function add

 > class Functor m => Monad' m where
 >       return' :: a -> m a
 >       join' :: m (m a) -> m a
 >       join' x = bind' x id
 >       bind' :: m a -> (a -> m b) -> m b
 >       bind' ma k = join' (fmap k ma) -- calls base class function fmap

As a solution I would suggest that newtype deriving a class instance is only allowed if all base classes instances are also derived using newtype deriving. This presents problems for Show and Read, because they cannot be derived in that way. It will, however, catch problems with most other classes.

Change History

Changed 6 years ago by simonpj

Thinking about this some more, I'm not sure that Twan's story is right. The declaration

newtype Meter = Meter Int deriving (Show2)

means "please behave as if I had written this:"

newtype Meter = Meter Int
instance Show2 Meter where
   show2 (Meter i) = show2 i

That is, it just saves typing all those wrappers and unwrappers. And that would give exactly the results you see.

Why does GHC require a Show instance for Meter too? Because Show is a superclass of Show2. So you can write

f :: Show2 a => a -> (String, String)
f x = (show x, show2 x)

And calling (f (Meter 3)) correctly yields ("Meter 1", "1").

Originally GHC had this wrong. I had implemented newtype deriving by literally using the (Show2 Int) dictionary as the (Show2 Meter) dictionary; and that meant that f returned ("1", "1"). Now GHC is careful to contruct the dictionary for (Show2 Meter) by combining the superclass dictionary (Show Meter) with the methods extracted from (Show2 Int).

In short, I now think that this is not a bug at all. I think what you want to say in this case, instead of using the "newtype deriving" feature, is simply the old Haskell 98 instance decl:

instance Show2 Meter

meaning "fill in the default methods from the class decl".

The other possibility I can think of is the following modification to the "newtype deriving" rules. For a "newtype derived" instance, generate an instance decl that:

  • When there is a default method declaration in the class, use that
  • When there is no default method declaration, generate the boilerplate newtype-unwrapping code

Example: suppose Show2 had more methods:

class Show a => Show2 a where
  show2 :: a -> String
  show2 = show

  extra :: a -> Int

instance Show2 Int where
  extra n = n

Then saying

data Meter = Meter Int deriving( Show, Show2 )

would generate the instance

instance Show2 Meter where
  show2 = show                -- Use default method
  extra (Meter i) = extra i   -- Use boilerplate

I am unsure what the consequences of this change would be, but it'd be fairly easy to implement. Simon

Changed 6 years ago by simonpj

  • status changed from new to closed
  • resolution set to invalid

No rejoinders, so closing this as 'invalid'. But I'll add a pointer to this exchange to the source code, with Note [Newtype deriving superclasses] in TcDeriv.

Simon

Changed 5 years ago by igloo

  • milestone changed from 6.8 branch to 6.8.2

Changed 5 years ago by simonmar

  • architecture changed from Unknown to Unknown/Multiple

Changed 5 years ago by simonmar

  • os changed from Unknown to Unknown/Multiple
Note: See TracTickets for help on using tickets.