
The way I've been describing GND all along has been an abbreviation. GHC does not coerce a dictionary from, say, Ord Int to Ord Age. Instead, GHC mints a fresh dictionary for Ord Age where all the methods are implemented as coerced versions of the methods for Ord Int. (I'm not sure why it's implemented this way, which is why I've elided this detail in just about every conversation on the topic.) With this in mind, I have a proposal:
1) All parameters of all classes have nominal role.
2) Classes also store one extra bit per parameter, saying whether all uses of that parameter are representational. Essentially, this bit says whether that parameter is suitable for GND. (Currently, we could just store for the last parameter, but we can imagine extensions to the GND mechanism for other parameters.)
Yes, this seems right. And NOW I finally realise why GHC implements GND like this. Consider
class Show a => C a where { op :: a -> a }
instance C Int where ...
newtype Age = Age Int deriving( Show, C )
Here we want to make a (C Age) dictionary that use the (C Int) version of 'op'. But the superclass for (Show Age) must not use the (Show Int) version of 'show'! It must use Age's own version of Show.
So we I think Richard's proposal is spot on. Go for it. Can you work on that, Richard?
Simon
From: Glasgow-haskell-users [mailto:glasgow-haskell-users-bounces@haskell.org] On Behalf Of Richard Eisenberg
Sent: 09 October 2013 20:21
To: Edward Kmett
Cc: Simon Peyton-Jones; glasgow-haskell-users@haskell.org Mailing List
Subject: Re: default roles
Now I think we're on the same page, and I *am* a little worried about the sky falling because of this. (That's not a euphemism -- I'm only a little worried.)
Well, maybe I should be more worried.
The whole idea of roles is to protect against type-unsoundness. They are doing a great job of that here -- no problem that we've discussed in this thread is a threat against type safety.
The issue immediately at hand is about coherence (or perhaps you call it confluence) of instances. Roles do not address the issue of coherence at all, and thus they fail to protect against coherence attacks. It would take More Thought to reformulate roles (or devise something else) to handle coherence.
It's worth pointing out that this isn't a new problem, exactly. Bug #8338 shows a way to produce incoherence using only the GADTs extension. (It does need 4 modules, though.) I conjecture that incoherence is also possible through GeneralizedNewtypeDeriving, both as it existed in GHC 7.6.3 and in 7.8, so it's not an issue with Coercible, exactly. It's just that Coercible allows you to get incoherence with so much less fuss than before!
Wait! I have an idea!
The way I've been describing GND all along has been an abbreviation. GHC does not coerce a dictionary from, say, Ord Int to Ord Age. Instead, GHC mints a fresh dictionary for Ord Age where all the methods are implemented as coerced versions of the methods for Ord Int. (I'm not sure why it's implemented this way, which is why I've elided this detail in just about every conversation on the topic.) With this in mind, I have a proposal:
1) All parameters of all classes have nominal role.
2) Classes also store one extra bit per parameter, saying whether all uses of that parameter are representational. Essentially, this bit says whether that parameter is suitable for GND. (Currently, we could just store for the last parameter, but we can imagine extensions to the GND mechanism for other parameters.)
Because GND is implemented using coercions on each piece instead of wholesale, the nominal roles on classes won't get in the way of proper use of GND. An experiment (see below for details) also confirms that even superclasses work well with this idea -- the superclasses aren't coerced.
Under this proposal, dictionaries can never be coerced, but GND would still seem to work.
Thoughts?
Richard
Experiment:
newtype Age = MkAge Int
instance Eq Age where
_ == _ = False
deriving instance Ord Age
useOrdInstance :: Ord a => a -> Bool
useOrdInstance x = (x == x)
What does `useOrdInstance (MkAge 5)` yield? It yields `False` (in HEAD). This means that the existing GND mechanism (I didn't change anything around this part of the code) uses superclass instances for the *newtype*, not for the *base type*. So, even with superclasses, class dictionaries don't need to be coerced.
On Oct 9, 2013, at 2:52 PM, Edward Kmett
class C a where c_meth :: a -> a -> Bool
Then, your example leads to the same embarrassing state of affairs: coercing a dictionary for (C Int) to one for (C Bar). But, I would argue that we still want C's parameter to have a representational role. Why? Consider this:
data Blargh = ... instance C Blargh where ...
newtype Baz = MkBaz Blargh deriving C
We want that last line to work, using GeneralizedNewtypeDeriving. This hinges on C's parameter's role being representational.
I think that what you've witnessed is a case of bug #8338 (http://ghc.haskell.org/trac/ghc/ticket/8338). This is a problem, in my view, and it seems to touch on roles, but I'm not completely sure of their relationship.
So, I think that classes should keep their representational roles (regardless of the decision on datatypes -- Haskell doesn't really support "abstract" classes), but perhaps we have to find a way to stop these incoherent instances from forming. Maybe the use of a constraint makes a datatype's role be nominal?
Richard
On Oct 9, 2013, at 1:55 PM, Edward Kmett
module Set (Set) where -- note: no constructors exported!
data Set a = MkSet [a] insert :: Ord a => a -> Set a -> Set a ...
{-# LANGUAGE GeneralizedNewtypeDeriving, StandaloneDeriving #-} module Client where
import Set
newtype Age = MkAge Int deriving Eq
instance Ord Age where (MkAge a) `compare` (MkAge b) = b `compare` a -- flip operands, reversing the order
class HasSet a where getSet :: Set a
instance HasSet Int where getSet = insert 2 (insert 5 empty)
deriving instance HasSet Age
good :: Set Int good = getSet
bad :: Set Age bad = getSet
According to the way GND works, `good` and `bad` will have the same runtime representation. But, using Set operations on `bad` would indeed be bad -- because the Ord instance for Age is different than that for Int, Set operations will fail unexpectedly on `bad`. The problem is that Set should really be abstract, but we've been able to break this abstraction with GND. Note that there is no type error in these operations, just wrong behavior. So, if we default to *no* GND, then the "deriving" line above would have an error and this problem wouldn't happen. If we default to *allowing* GND, then the writer of Set would have to include
type role Set nominal in the definition of the Set module to prevent the use of GND. (Why that peculiar annotation? See the linked further reading, above.)
Although it doesn't figure in this example, a library writer who wishes to allow GND in the default-no scenario would need a similar annotation
type role Foo representational to allow it.
There are clearly reasons for and against either decision, but which is better? Let the users decide! Discussion time: 2 weeks. Thanks! Richard _______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.orgmailto:Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users _______________________________________________ Glasgow-haskell-users mailing list Glasgow-haskell-users@haskell.orgmailto:Glasgow-haskell-users@haskell.org http://www.haskell.org/mailman/listinfo/glasgow-haskell-users