Re: Proposal for stand-alone deriving declarations?

What I implemented in GHC is an extension of the proposal below. The proposal just mentions: deriving Class for Type In GHC I also added a form for newtype deriving of multi-parameter type classes: deriving (Class t1 ... tn) for Type I think that it's close to what we ended up with when talking about it at the Hackathon. My intuition about this syntax is that except for the "for Type" part, it looks the same as a normal deriving clause. The "for" part is just there to connect it to a data/newtype declaration. This lets it pretty much use the normal code for deriving declarations. Stand-alone deriving declarations are currently a little bit weaker than normal deriving clauses, since the current implementation does not let you reference the type arguments of a newtype in the arguments of an MPTC. See my response to Bulat on cvs-ghc@haskell.org for more details. /Björn
A suggestion re syntax: With the newtype-deriving extension, the instances named in a deriving clause are not just class names, but partial applications of class names to all but the last argument. Why all but the last one? Because the last one is the type being defined. Once deriving clauses are separated from type definitions, then there is no type being defined any more--hence the need for "for Type" in your syntax, and the introduction of another keyword. But why single out one class parameter, once deriving clauses are separated from type definitions? Why not simply write the FULL application of the class in the deriving list? Thus: deriving (Eq Foo, Ord Foo) instead of deriving (Eq, Ord) for Foo I find the former syntax clearer and more readable, actually. John

John Hughes wrote:
What I implemented in GHC is an extension of the proposal below. The proposal just mentions:
deriving Class for Type
In GHC I also added a form for newtype deriving of multi-parameter type classes:
deriving (Class t1 ... tn) for Type
I think that it's close to what we ended up with when talking about it at the Hackathon. My intuition about this syntax is that except for the "for Type" part, it looks the same as a normal deriving clause. The "for" part is just there to connect it to a data/newtype declaration. This lets it pretty much use the normal code for deriving declarations.
Stand-alone deriving declarations are currently a little bit weaker than normal deriving clauses, since the current implementation does not let you reference the type arguments of a newtype in the arguments of an MPTC. See my response to Bulat on cvs-ghc@haskell.org for more details.
/Björn
A suggestion re syntax:
With the newtype-deriving extension, the instances named in a deriving clause are not just class names, but partial applications of class names to all but the last argument. Why all but the last one? Because the last one is the type being defined. Once deriving clauses are separated from type definitions, then there is no type being defined any more--hence the need for "for Type" in your syntax, and the introduction of another keyword. But why single out one class parameter, once deriving clauses are separated from type definitions? Why not simply write the FULL application of the class in the deriving list? Thus:
deriving (Eq Foo, Ord Foo)
instead of
deriving (Eq, Ord) for Foo
I find the former syntax clearer and more readable, actually.
John
The exact same thing is currently being discussed on cvs-ghc@haskell.org, and we seem to have reached a consensus to adopt the same syntax that you are proposing. Well, I was the only one who ever like the original syntax, so "reached a consensus" means "everybody else convinced me". I'll implement this syntax instead and then write up a Haskell' proposal. /Björn

On 10/6/06, Björn Bringert
John Hughes wrote:
deriving (Eq Foo, Ord Foo)
instead of
deriving (Eq, Ord) for Foo
I find the former syntax clearer and more readable, actually.
John
I'll implement this syntax instead and then write up a Haskell' proposal.
I am sure that it was already argued at great length, but I think it is wrong to start the declaration with "deriving." I believe that "derive instance" fits much better into the language. I understand the desire to avoid adding new keywords but I think that something along the lines of what was done for "for" could be done here for "derive." Regards, Brian

On 8 okt 2006, at 18.22, Brian Smith wrote:
On 10/6/06, Björn Bringert
wrote: John Hughes wrote: deriving (Eq Foo, Ord Foo)
instead of
deriving (Eq, Ord) for Foo
I find the former syntax clearer and more readable, actually.
John
I'll implement this syntax instead and then write up a Haskell' proposal.
I am sure that it was already argued at great length, but I think it is wrong to start the declaration with "deriving." I believe that "derive instance" fits much better into the language. I understand the desire to avoid adding new keywords but I think that something along the lines of what was done for "for" could be done here for "derive."
I agree that "derive" would be nicer, but as you say, the problem is that it would add a new keyword. Since the declaration would then start with "derive", I don't that think it could easily be made into a special identifier. A deriving declaration would look like this: derive Eq Foo which looks just like the beginning of a declaration of a function called "derive" which does some pattern matching, if derive can also be an identifier. /Björn

On 10/8/06, Bjorn Bringert
I agree that "derive" would be nicer, but as you say, the problem is that it would add a new keyword. Since the declaration would then start with "derive", I don't that think it could easily be made into a special identifier. A deriving declaration would look like this:
derive Eq Foo
which looks just like the beginning of a declaration of a function called "derive" which does some pattern matching, if derive can also be an identifier.
That is why I suggested "derive instance." Then the only ambiguity comes when "derive" is used as a name immediately before an instance declaration. which should be really, really rare. It's not 100% backward compatible but it is a better compromise than using "deriving." Regards, Brian

On 8 okt 2006, at 20.11, Brian Smith wrote:
On 10/8/06, Bjorn Bringert
wrote: I agree that "derive" would be nicer, but as you say, the problem is that it would add a new keyword. Since the declaration would then start with "derive", I don't that think it could easily be made into a special identifier. A deriving declaration would look like this: derive Eq Foo
which looks just like the beginning of a declaration of a function called "derive" which does some pattern matching, if derive can also be an identifier.
That is why I suggested "derive instance." Then the only ambiguity comes when "derive" is used as a name immediately before an instance declaration. which should be really, really rare. It's not 100% backward compatible but it is a better compromise than using "deriving."
Oops, sorry. I should read more carefully. Would that work? I guess I'll have to try. I think that after layout resolution there can't be any identifiers right before instance declarations, so I guess that wouldn't be a problem. /Björn

On 10/6/06, John Hughes
deriving (Eq Foo, Ord Foo)
instead of
deriving (Eq, Ord) for Foo
So what does newtype Foo a = Foo a newtype Bar b = Bar b class C a b deriving (C (Foo a) (Bar b)) mean? I could see it meaning any or all of the following: instance (C (Foo a) b) => (C (Foo a) (Bar b)) instance (C a (Bar b)) => (C (Foo a) (Bar b)) instance (C a b) => (C (Foo a) (Bar b)) Perhaps I am misunderstanding? Mike

On Fri, Oct 06, 2006 at 10:39:39AM -0500, Michael Shulman wrote:
On 10/6/06, John Hughes
wrote: deriving (Eq Foo, Ord Foo)
instead of
deriving (Eq, Ord) for Foo
So what does
newtype Foo a = Foo a newtype Bar b = Bar b class C a b deriving (C (Foo a) (Bar b))
mean? I could see it meaning any or all of the following:
instance (C (Foo a) b) => (C (Foo a) (Bar b)) instance (C a (Bar b)) => (C (Foo a) (Bar b)) instance (C a b) => (C (Foo a) (Bar b))
this is why we should make this explicit when deriving "complex" newtype instances, so we would write exactly the instance we want to derive:
deriving (C (Foo a) b) => (C (Foo a) (Bar b)) deriving (C a (Bar b)) => (C (Foo a) (Bar b)) deriving (C a b) => (C (Foo a) (Bar b))
respectively. John -- John Meacham - ⑆repetae.net⑆john⑈

The thread about "stand-alone" deriving is long-ish now, so I have summarised the issues here:
http://haskell.org/haskellwiki/GHC/StandAloneDeriving
Perhaps those who are interested can add their thoughts? Bjorn is busy at the moment, but I think he'll get back to the implementation in a while, so now is a good time to refine the design.
I've tried to note all the issues that came up by email, but I might have missed.
I put it on the GHC wiki, because I'm not proposing it for Haskell'. (Someone else is free to do so, of course.)
Simon
| -----Original Message-----
| From: haskell-prime-bounces@haskell.org [mailto:haskell-prime-bounces@haskell.org] On Behalf Of
| John Meacham
| Sent: 30 October 2006 05:17
| To: haskell-prime@haskell.org
| Subject: Re: Proposal for stand-alone deriving declarations?
|
| On Fri, Oct 06, 2006 at 10:39:39AM -0500, Michael Shulman wrote:
| > On 10/6/06, John Hughes

On 11/1/06, Simon Peyton-Jones
The thread about "stand-alone" deriving is long-ish now, so I have summarised the issues here: http://haskell.org/haskellwiki/GHC/StandAloneDeriving
I think it is difficult to discuss things via a Wiki. I tried to add my comments to the page and it took a lot of edits due to a collision with BrianH's edit. Plus, it is not clear to me how agreement is to be indicated. For example, I think that Brian H's idea really good. Basically, the syntax for instances and derived instances would be identical, but derived instances would just omit the "where" clause, while non-derived instances would need the "where" clause. For that to work, the rules for when a context is required need to be the same for derived and non-derived instances, which I also think is a good idea. It is a much better idea than my "derive instance" suggestion, and I no longer support it. - Brian Smith

On Wed, Nov 01, 2006 at 12:30:31PM -0600, Brian Smith wrote:
I think it is difficult to discuss things via a Wiki.
Yes, a mailing list is much better for discussion. In response to your point about duplicate derived instances, yes, it would make derived instances different from explicit ones, but as I've argued before, I think it's unavoidable: http://www.haskell.org/pipermail/haskell-prime/2006-October/thread.html#1739 If you accept that this is the primary use of the standalone feature, such conflicts will happen all the time.

| > I think it is difficult to discuss things via a Wiki. | | Yes, a mailing list is much better for discussion. I agree. But the Wiki is an excellent place to specify alternative designs, and summarise their advantages and disadvantages. Otherwise alternative designs can get lost in the twists and turns of a long thread. So: discuss by email, record outcomes and articulate alternative design choices on the Wiki Simon

On 11/1/06, Ross Paterson
On Wed, Nov 01, 2006 at 12:30:31PM -0600, Brian Smith wrote: In response to your point about duplicate derived instances, yes, it would make derived instances different from explicit ones, but as I've argued before, I think it's unavoidable:
http://www.haskell.org/pipermail/haskell-prime/2006-October/thread.html#1739
If you accept that this is the primary use of the standalone feature, such conflicts will happen all the time.
How an instance is defined (explicitly or derived) should have nothing to do with how it is imported/exported in a module. In particular, I think having features like : import M1 hiding (instance C T) and module M hiding (instance C T) would eliminate the need for special-case handling of derived instances (if two imported modules happen to derive the same instances, you can just hide the instances from one of them). Instance hiding is an important feature in its own right. - Brian

On Wed, Nov 01, 2006 at 03:15:38PM -0600, Brian Smith wrote:
How an instance is defined (explicitly or derived) should have nothing to do with how it is imported/exported in a module.
In particular, I think having features like : import M1 hiding (instance C T) and module M hiding (instance C T) would eliminate the need for special-case handling of derived instances (if two imported modules happen to derive the same instances, you can just hide the instances from one of them). Instance hiding is an important feature in its own right.
selective importing and exporting of instances is a really tricky technical issue for a lot of reasons, and a bad idea for many others. For a quick example, imagine a Set created with some operations using one Ord instance and other operations using another. the globalness of instance declarations is a great tool. Fortunately, newtype deriving (hopefully extended as I have mentioned on this thread) and rank-n polymorphism make it pretty much unneeded. John -- John Meacham - ⑆repetae.net⑆john⑈

On Wed, Nov 01, 2006 at 03:51:57PM -0800, John Meacham wrote:
On Wed, Nov 01, 2006 at 03:15:38PM -0600, Brian Smith wrote:
Instance hiding is an important feature in its own right.
selective importing and exporting of instances is a really tricky technical issue for a lot of reasons, and a bad idea for many others.
As an example of the technical difficulties, recall that with ghc -fglasgow-exts and hugs -98 defer reducing contexts using instances until forced to (see FlexibleContexts). If instances can come and go, this will be even more confusing.

Hello Brian, Thursday, November 2, 2006, 12:15:38 AM, you wrote:
In particular, I think having features like : import M1 hiding (instance C T) and module M hiding (instance C T) would eliminate the need for special-case handling of derived instances (if two imported modules happen to derive the same instances, you can just hide the instances from one of them). Instance hiding is an important feature in its own right.
i vote for this feature too. in my AltBinary library, there are many different ways to serialize strings, for example. i want to provide default "instance Binary String" for quick&dirty use and ability to hide just this instance in the case when user need to overwrite it. now i forced to put all instance declarations in separate file and suggest user to copy this file into his project and make appropriate edits! also, expicit instance imports was in prewvious Haskell versions. so i think it will be great to use implicit importing of all instances by default but allow to hide all instances or specific instance and import just the instance required: import M (instance Binary String) -- hides all other Binary instances module M hiding (instance Binary) -- hides all Binary instances -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

Bulat Ziganshin
import M1 hiding (instance C T)
i vote for this feature too. also, expicit instance imports was in prewvious Haskell versions.
I'm fairly confident that Haskell has never historically permitted the explicit import and export of instances. It is not semantically sound, unless you are able to name the instances separately, but Haskell has always had anonymous instances, with only one instance permitted per concrete type. However, if Haskell' gets PolymorphicComponents in records, then at last you will be able to construct dictionaries explicitly, have more than one per type, name them, and pass them around. (But the downside is that if you do dictionaries explicitly, you will no longer have access to the class machinery to do predicate resolution and simplification for you - that would become a manual process. But you only pay the price if you want the explicitness.) Regards, Malcolm

"Brian Smith"
Basically, the syntax for instances and derived instances would be identical, but derived instances would just omit the "where" clause, while non-derived instances would need the "where" clause.
This is a really _bad_ idea for readability. In a year's time, when this discussion is forgotten, who would be able to tell at a glance the deep and real semantic difference between the following two instance decls? instance Num (Bar z) where and instance Num (Bar z) The former declares that _no_ methods are defined (except for defaults), and the latter, with your proposal, that _all_ methods are defined. The real killer is that both of these decls are already valid in Haskell'98, but they mean the _same_ thing (the former, if you were wondering). This is a sure recipe for introducing new and subtle bugs into existing bug-free programs. Regards, Malcolm

On 11/1/06, Malcolm Wallace
"Brian Smith"
writes: Basically, the syntax for instances and derived instances would be identical, but derived instances would just omit the "where" clause, while non-derived instances would need the "where" clause.
This is a really _bad_ idea for readability. In a year's time, when this discussion is forgotten, who would be able to tell at a glance the deep and real semantic difference between the following two instance decls?
instance Num (Bar z) where and instance Num (Bar z)
The former declares that _no_ methods are defined (except for defaults), and the latter, with your proposal, that _all_ methods are defined. The real killer is that both of these decls are already valid in Haskell'98, but they mean the _same_ thing (the former, if you were wondering).
This is a sure recipe for introducing new and subtle bugs into existing bug-free programs.
Malcolm, thank you for pointing that out. To be honest, I had not read this sentence of the report (section 4.3.2 of http://haskell.org/onlinereport/decls.html): "If no binding is given for some class method then the corresponding default class method in the class declaration is used (if present); if such a default does not exist then the class method of this instance is bound to undefined and no compile-time error results." Personally, I think that the part after the semicolon is the part that leads to subtle bugs, not Brian H's proposal. Why is this not considered a compile-time error? There was a previous mention of it on the list [1] but there was no explanation given. I think it would be better if the report stated this instead: "If no binding is given for some class method then the corresponding default class method in the class declaration is used (if present); if such a default does not exist then a compile-time error results." [1] http://www.haskell.org/pipermail/haskell/2003-May/011899.html - Brian

Hello Malcolm, Thursday, November 2, 2006, 12:46:43 AM, you wrote:
instance Num (Bar z) where and instance Num (Bar z)
The former declares that _no_ methods are defined (except for defaults), and the latter, with your proposal, that _all_ methods are defined. The
i join to this note. moreover, currently GHC supports "generics for the masses" that may mean very subtle semantic changes between code generated by these two forms :)) i think it will be better to use derive/deriving/derived prepended to the former: derive instance Num (Bar z) derive instance Num z => Num (Bar z) and allow generics/TH/other forms of user-specified deriving to catch such declarations and provide alternative to compiler-wired deriving mechanism. so we can imagine that we discuss some general deriving syntax that in future will be reused by new Haskell extensions -- Best regards, Bulat mailto:Bulat.Ziganshin@gmail.com

On 11/2/06, Bulat Ziganshin
Hello Malcolm,
Thursday, November 2, 2006, 12:46:43 AM, you wrote:
instance Num (Bar z) where and instance Num (Bar z)
The former declares that _no_ methods are defined (except for defaults), and the latter, with your proposal, that _all_ methods are defined. The
i join to this note. moreover, currently GHC supports "generics for the masses" that may mean very subtle semantic changes between code generated by these two forms :))
Thank you guys for your responses. Effectively, every class method has a default implementation: if the programmer does not supply a default implementation, then the the "default" default implementation is undefined. But, does it make sense to create a class with default methods, but which is also derivable, such that the derived instance declarations are different than if the default implementations are chosen? When would it make sense to allow: derive instance A B and instance A B and have them both be valid (not at the same time, of course), but mean totally different things? I think it would be very confusing, especially for the innocent user who simply forgets the "derive" psudo-keyword. Would it be possible to make deriving a special case of defaulting? That is, could we define every derivable class so that every method defaulted, instead of to undefined, to some "deriving magic," perhaps expressible in Template Haskell? Perhaps we could say that all classes that have all their methods defaulted are then derivable? - Brian
participants (10)
-
Bjorn Bringert
-
Björn Bringert
-
Brian Smith
-
Bulat Ziganshin
-
John Hughes
-
John Meacham
-
Malcolm Wallace
-
Michael Shulman
-
Ross Paterson
-
Simon Peyton-Jones