GHC: Ticket #1220: Newtype deriving should only work if superclasses are newtype-derived
http://ghc.haskell.org/trac/ghc/ticket/1220
<p>
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.
</p>
<pre class="wiki"> > {-# OPTIONS_GHC -fglasgow-exts #-}
</pre><p>
This problem comes up when an instance method calls a method of a base
class. Consider the following useless example:
</p>
<pre class="wiki"> > class Show a => Show2 a where
> show2 :: a -> String
> show2 = show
>
> instance Show2 Int
</pre><p>
Now consider a type deriving Show2, but having a different
implementation of Show (also derived).
</p>
<pre class="wiki"> > newtype Meter = Meter Int
> deriving (Eq, Show, Show2)
</pre><p>
Now, for show2 the derived Show instance (which also shows the
constructor name) is *not* used, instead the Show Int instance is used:
</p>
<pre class="wiki">] > show2 (Meter 1)
] "1"
] > show (Meter 1)
] "Meter 1"
</pre><p>
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!
</p>
<pre class="wiki">] 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'
</pre><p>
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:
</p>
<pre class="wiki"> > 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
</pre><p>
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.
</p>
en-usGHChttp://ghc.haskell.org/trac/ghc/chrome/site/ghc_logo.png
http://ghc.haskell.org/trac/ghc/ticket/1220
Trac 1.0.9simonpjWed, 14 Mar 2007 10:56:25 GMT
http://ghc.haskell.org/trac/ghc/ticket/1220#comment:1
http://ghc.haskell.org/trac/ghc/ticket/1220#comment:1
<p>
Thinking about this some more, I'm not sure that Twan's story is right. The declaration
</p>
<pre class="wiki">newtype Meter = Meter Int deriving (Show2)
</pre><p>
means "please behave as if I had written this:"
</p>
<pre class="wiki">newtype Meter = Meter Int
instance Show2 Meter where
show2 (Meter i) = show2 i
</pre><p>
That is, it just saves typing all those wrappers and unwrappers. And that would give exactly the results you see.
</p>
<p>
Why does GHC require a <tt>Show</tt> instance for <tt>Meter</tt> too? Because <tt>Show</tt> is a superclass of <tt>Show2</tt>. So you can write
</p>
<pre class="wiki">f :: Show2 a => a -> (String, String)
f x = (show x, show2 x)
</pre><p>
And calling <tt>(f (Meter 3))</tt> correctly yields <tt>("Meter 1", "1")</tt>.
</p>
<p>
Originally GHC had this wrong. I had implemented newtype deriving by literally using the <tt>(Show2 Int)</tt> dictionary as the <tt>(Show2 Meter)</tt> dictionary; and that meant that <tt>f</tt> returned ("1", "1"). Now GHC is careful to contruct the dictionary for <tt>(Show2 Meter)</tt> by combining the superclass dictionary <tt>(Show Meter)</tt> with the methods extracted from <tt>(Show2 Int)</tt>.
</p>
<p>
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:
</p>
<pre class="wiki">instance Show2 Meter
</pre><p>
meaning "fill in the default methods from the class decl".
</p>
<p>
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:
</p>
<ul><li>When there is a default method declaration in the class, use that
</li><li>When there is no default method declaration, generate the boilerplate newtype-unwrapping code
</li></ul><p>
Example: suppose <tt>Show2</tt> had more methods:
</p>
<pre class="wiki">class Show a => Show2 a where
show2 :: a -> String
show2 = show
extra :: a -> Int
instance Show2 Int where
extra n = n
</pre><p>
Then saying
</p>
<pre class="wiki">data Meter = Meter Int deriving( Show, Show2 )
</pre><p>
would generate the instance
</p>
<pre class="wiki">instance Show2 Meter where
show2 = show -- Use default method
extra (Meter i) = extra i -- Use boilerplate
</pre><p>
I am unsure what the consequences of this change would be, but it'd be fairly easy to implement.
Simon
</p>
TicketsimonpjMon, 12 Nov 2007 15:39:20 GMTstatus changed; resolution set
http://ghc.haskell.org/trac/ghc/ticket/1220#comment:2
http://ghc.haskell.org/trac/ghc/ticket/1220#comment:2
<ul>
<li><strong>status</strong>
changed from <em>new</em> to <em>closed</em>
</li>
<li><strong>resolution</strong>
set to <em>invalid</em>
</li>
</ul>
<p>
No rejoinders, so closing this as 'invalid'. But I'll add a pointer to this exchange to the source code, with <tt>Note [Newtype deriving superclasses]</tt> in <tt>TcDeriv</tt>.
</p>
<p>
Simon
</p>
TicketiglooFri, 14 Dec 2007 17:17:19 GMTmilestone changed
http://ghc.haskell.org/trac/ghc/ticket/1220#comment:3
http://ghc.haskell.org/trac/ghc/ticket/1220#comment:3
<ul>
<li><strong>milestone</strong>
changed from <em>6.8 branch</em> to <em>6.8.2</em>
</li>
</ul>
TicketsimonmarTue, 30 Sep 2008 15:40:10 GMTarchitecture changed
http://ghc.haskell.org/trac/ghc/ticket/1220#comment:4
http://ghc.haskell.org/trac/ghc/ticket/1220#comment:4
<ul>
<li><strong>architecture</strong>
changed from <em>Unknown</em> to <em>Unknown/Multiple</em>
</li>
</ul>
TicketsimonmarTue, 30 Sep 2008 15:51:15 GMTos changed
http://ghc.haskell.org/trac/ghc/ticket/1220#comment:5
http://ghc.haskell.org/trac/ghc/ticket/1220#comment:5
<ul>
<li><strong>os</strong>
changed from <em>Unknown</em> to <em>Unknown/Multiple</em>
</li>
</ul>
Ticket