www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - How will we fix opEquals?

reply Don <nospam nospam.com> writes:
Andrei once stated a worthy goal: as far as possible, const should be 
opt-in: it should be possible to code without requiring const on everything.

opEquals() is in conflict with this, since it is a member function of 
Object.

(1) If it is a const member function, then it will have a viral effect 
on all objects -- any function called by opEquals will have to be marked 
const.
(2) If it is not const, then const objects cannot be compared!

Currently, it's not const,  but the problem isn't visible because of 
compiler bug 5080. (Same problem applies to opCmp).

How will we break this dilemma?
Feb 10 2011
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday 10 February 2011 00:19:38 Don wrote:
 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on
 everything.
 
 opEquals() is in conflict with this, since it is a member function of
 Object.
 
 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be marked
 const.
 (2) If it is not const, then const objects cannot be compared!
 
 Currently, it's not const,  but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).
 
 How will we break this dilemma?
Honestly, I think that opEquals should just be const. It doesn't generally make any sense for it not to be const anyway. And if Object's opEquals _isn't_ const, then const in general is screwed. opEquals, toString, opCmp, and toHash _all_ need to be const for const and immutable classes to work in general. I just don't think that it's realistic to say that a programmer can totally ignore const if they don't want to worry about it. But for the most part, what does it matter? So, there's a few functions which they have to mark as const, because that's the way that they are in Object. That doesn't mean that it's viral throughout their program. They can just skip const everywhere else. The _one_ thing that I can think of that's problematic about that approach is caching - which would matter for toHash and possibly toString (though changing to writeTo would likely make caching on that a non-option anyway). But if they _really_ want caching, then can't they just add a second toHash to their class and make that one non-const? That one could cache the result, and then the const version could use the cached result if it existed. Realistically, Object _must_ be const-correct for const to work. And if Object is const-correct, then anyone overriding those const methods _must_ worry about const for those functions. I don't see any way around that. Now, they don't have to make any other functions const or declare any variables which are const or do anything else with const whatsoever, but toString/writeTo, opEquals, opCmp, and toHash _must_ be const or const and immutable objects are screwed, because they couldn't call them. Saying that no one should have to worry about const if they don't want to is a noble though, I suppose, but I don't think that it's entirely realistic. const is part of the language, and some things just plain have to be const to work. And given the prevalence of immutable with regards to threads and the like, you're going to be forced to use const in many cases anyway. I don't think that it's all that big a deal with these 4 functions are const. The impact on the programmer is minimal. They can _almost_ ignore const completely, since they can still ignore it pretty much everywhere else other than with the overriden const Object functions. - Jonathan M Davis
Feb 10 2011
next sibling parent reply so <so so.so> writes:
 Saying that no one should have to worry about const if they don't want  
 to is a
 noble though, I suppose, but I don't think that it's entirely realistic.  
 const
 is part of the language, and some things just plain have to be const to  
 work.
 And given the prevalence of immutable with regards to threads and the  
 like,
 you're going to be forced to use const in many cases anyway.

 I don't think that it's all that big a deal with these 4 functions are  
 const.
 The impact on the programmer is minimal. They can _almost_ ignore const
 completely, since they can still ignore it pretty much everywhere else  
 other
 than with the overriden const Object functions.

 - Jonathan M Davis
Problem is not having to provide const for these functions, it is the lock-in we introduce by marking them const. These functions would be const most of the times, if not all. Still, it is hard to rule the other case out completely i suppose.
Feb 10 2011
next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday 10 February 2011 01:26:14 so wrote:
 Saying that no one should have to worry about const if they don't want
 to is a
 noble though, I suppose, but I don't think that it's entirely realistic.
 const
 is part of the language, and some things just plain have to be const to
 work.
 And given the prevalence of immutable with regards to threads and the
 like,
 you're going to be forced to use const in many cases anyway.
 
 I don't think that it's all that big a deal with these 4 functions are
 const.
 The impact on the programmer is minimal. They can _almost_ ignore const
 completely, since they can still ignore it pretty much everywhere else
 other
 than with the overriden const Object functions.
 
 - Jonathan M Davis
Problem is not having to provide const for these functions, it is the lock-in we introduce by marking them const. These functions would be const most of the times, if not all. Still, it is hard to rule the other case out completely i suppose.
The thing is that opEquals, opCmp, toHash, and toString/writeTo _must_ be const for const and immutable objects to be able to use them. It's non-negotiable unless Object is breaking the type system (which would _not_ be good). That being the case, it doesn't matter how much we might want to avoid forcing const on people. We _must_ have it there, so anyone overriding those functions _must_ use it for those functions. They could create non-const versions in addition to the const ones, but they _must_ create the const versions. They're stuck. There's no way around it without breaking the type system. structs avoid the problem, because they don't have to worry about polymorphism, but classes obviously do. Now, that forces people to use const on _4_ functions per class - that's it. And that's assuming that they want to override them all. They can ignore const beyond that. So, they _cannot_ ignore const completely. It's just not practical. However, they _can_ _mostly_ ignore it. So, it _does_ have an impact on those who don't wish to bother with const, but the impact should be minimal. - Jonathan M Davis
Feb 10 2011
next sibling parent Graham St Jack <Graham.StJack internode.on.net> writes:
Vote++

-- 
Graham St Jack
Feb 10 2011
prev sibling parent reply so <so so.so> writes:
 We _must_ have it there, so anyone overriding those functions _must_
 use it for those functions. They could create non-const versions in  
 addition to
 the const ones,
It is the whole point, they can't.
 but they _must_ create the const versions. They're stuck.
 There's no way around it without breaking the type system. structs avoid  
 the
 problem, because they don't have to worry about polymorphism, but classes
 obviously do.
Structs avoid because unlike classes structs don't share the same base class.
Feb 10 2011
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday 10 February 2011 21:50:45 so wrote:
 We _must_ have it there, so anyone overriding those functions _must_
 use it for those functions. They could create non-const versions in
 addition to
 the const ones,
It is the whole point, they can't.
Hmm. You're right (I just tried it). I was certain that you could. It's the kind of thing that you could do in C++, but I guess that D is pickier about overriding. You can certainly _add_ const, but I guess you can't take it away even if there's also a const version defined. I guess that part of the problem is that I so rarely use classes in D. I usually use structs. I'm sure that will change if I ever get the chance to write large programs in D, but until I can use it at work I'm not sure how likely that is. Well, then there's no caching of toHash unless you call a separate function to calculate and cache the result first, or you use a global variable to hold the cached value. That kind of sucks, but I think that const is far more valuable that the possibility of caching, and there _are_ ways to get the caching behavior in spite of const - they just aren't all that nice. - Jonathan M Davis
Feb 10 2011
prev sibling parent spir <denis.spir gmail.com> writes:
On 02/11/2011 07:13 AM, Jonathan M Davis wrote:
 We _must_ have it there, so anyone overriding those functions _must_
  >  use it for those functions. They could create non-const versions in
  >  addition to
  >  the const ones,
It is the whole point, they can't.
Hmm. You're right (I just tried it).
In the worst case, people who really, really, want it it non-const are left with: struct S { ... bool equals (ref S s) {...} } ... if (s1.equals(s2)) {...} I do not find it /that/ terrible (esp.compared to many other wrokarounds we commonly are forced to use for various reasons). The one bad case is if S belongs to the interface to client code. In other words, this is not an acceptable solution for library public types. But since const opEquals (and same reasoning for other methods) is only annoying for structs that (1) need custom equality predicate (2) call other funcs, else as member opEquals, (3) which themselves may be called by yet other funcs (else virality does not spread), then I guess very few cases remain. What do you think? Denis -- _________________ vita es estrany spir.wikidot.com
Feb 11 2011
prev sibling parent reply spir <denis.spir gmail.com> writes:
On 02/10/2011 10:26 AM, so wrote:
 Saying that no one should have to worry about const if they don't want to is a
 noble though, I suppose, but I don't think that it's entirely realistic. const
 is part of the language, and some things just plain have to be const to work.
 And given the prevalence of immutable with regards to threads and the like,
 you're going to be forced to use const in many cases anyway.

 I don't think that it's all that big a deal with these 4 functions are const.
 The impact on the programmer is minimal. They can _almost_ ignore const
 completely, since they can still ignore it pretty much everywhere else other
 than with the overriden const Object functions.

 - Jonathan M Davis
Problem is not having to provide const for these functions, it is the lock-in we introduce by marking them const. These functions would be const most of the times, if not all. Still, it is hard to rule the other case out completely i suppose.
Has it been considered for constness or such constraints not to be inherited? (Rather plain functype signature.) Denis -- _________________ vita es estrany spir.wikidot.com
Feb 10 2011
parent so <so so.so> writes:
 Has it been considered for constness or such constraints not to be  
 inherited? (Rather plain functype signature.)

 Denis
I asked something similar not more than a week ago, it is possible in theory i think but implementation is very problematic and requires a big shift in the meaning of constness.
Feb 10 2011
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 10 Feb 2011 03:37:58 -0500, Jonathan M Davis <jmdavisProg gmx.com>  
wrote:

 On Thursday 10 February 2011 00:19:38 Don wrote:
 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on
 everything.

 opEquals() is in conflict with this, since it is a member function of
 Object.

 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be marked
 const.
 (2) If it is not const, then const objects cannot be compared!

 Currently, it's not const,  but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).

 How will we break this dilemma?
Honestly, I think that opEquals should just be const. It doesn't generally make any sense for it not to be const anyway. And if Object's opEquals _isn't_ const, then const in general is screwed. opEquals, toString, opCmp, and toHash _all_ need to be const for const and immutable classes to work in general. I just don't think that it's realistic to say that a programmer can totally ignore const if they don't want to worry about it. But for the most part, what does it matter? So, there's a few functions which they have to mark as const, because that's the way that they are in Object. That doesn't mean that it's viral throughout their program. They can just skip const everywhere else.
This isn't really true. If you make opEquals const, then anything opEquals calls must be const. Inevitably, you need to make a lot of your object const, which then goes viral to things that object uses, etc. On the other hand, I don't think opt-in const is that worthy a goal. Things that are const, should be const. -Steve
Feb 10 2011
parent Graham St Jack <Graham.StJack internode.on.net> writes:
 This isn't really true.  If you make opEquals const, then anything 
 opEquals calls must be const.  Inevitably, you need to make a lot of 
 your object const, which then goes viral to things that object uses, etc.

 On the other hand, I don't think opt-in const is that worthy a goal.  
 Things that are const, should be const.

 -Steve
I totally agree - we need to take the plunge and roll const out through phobos. If const (and immutable too for that matter) is broken in subtle ways, then it needs to be fixed and then embraced. -- Graham St Jack
Feb 10 2011
prev sibling next sibling parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
On 10/02/11 8:19 AM, Don wrote:
 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on
 everything.

 opEquals() is in conflict with this, since it is a member function of
 Object.

 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be marked
 const.
 (2) If it is not const, then const objects cannot be compared!

 Currently, it's not const, but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).

 How will we break this dilemma?
It's not possible. You need logical const to make const opt-in. This has been argued many times by several people.
Feb 10 2011
next sibling parent so <so so.so> writes:
On Thu, 10 Feb 2011 10:58:25 +0200, Peter Alexander  
<peter.alexander.au gmail.com> wrote:

 On 10/02/11 8:19 AM, Don wrote:
 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on
 everything.

 opEquals() is in conflict with this, since it is a member function of
 Object.

 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be marked
 const.
 (2) If it is not const, then const objects cannot be compared!

 Currently, it's not const, but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).

 How will we break this dilemma?
It's not possible. You need logical const to make const opt-in. This has been argued many times by several people.
There might be a way involving something like inout attribute for functions. It would solve these problems and wouldn't intrude those that don't care constness.
Feb 10 2011
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/10/11 2:58 AM, Peter Alexander wrote:
 On 10/02/11 8:19 AM, Don wrote:
 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on
 everything.

 opEquals() is in conflict with this, since it is a member function of
 Object.

 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be marked
 const.
 (2) If it is not const, then const objects cannot be compared!

 Currently, it's not const, but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).

 How will we break this dilemma?
It's not possible.
It is, in fact, possible. We can define two opEquals overloads, for const and non-const. By default, the non-const version forwards to the const one. Andrei
Feb 10 2011
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 10 Feb 2011 10:19:50 -0500, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 2/10/11 2:58 AM, Peter Alexander wrote:
 On 10/02/11 8:19 AM, Don wrote:
 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on
 everything.

 opEquals() is in conflict with this, since it is a member function of
 Object.

 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be  
 marked
 const.
 (2) If it is not const, then const objects cannot be compared!

 Currently, it's not const, but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).

 How will we break this dilemma?
It's not possible.
It is, in fact, possible. We can define two opEquals overloads, for const and non-const. By default, the non-const version forwards to the const one.
This doesn't work. If you only define one, then you will have problems with hidden function errors, and/or inconsistencies. For example, how does .opEquals(Object obj1, Object obj2) know which version to call? I think the only true solution is to make it const and call it a day. -Steve
Feb 10 2011
next sibling parent reply Don <nospam nospam.com> writes:
Steven Schveighoffer wrote:
 On Thu, 10 Feb 2011 10:19:50 -0500, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:
 
 On 2/10/11 2:58 AM, Peter Alexander wrote:
 On 10/02/11 8:19 AM, Don wrote:
 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on
 everything.

 opEquals() is in conflict with this, since it is a member function of
 Object.

 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be 
 marked
 const.
 (2) If it is not const, then const objects cannot be compared!

 Currently, it's not const, but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).

 How will we break this dilemma?
It's not possible.
It is, in fact, possible. We can define two opEquals overloads, for const and non-const. By default, the non-const version forwards to the const one.
This doesn't work. If you only define one, then you will have problems with hidden function errors, and/or inconsistencies. For example, how does .opEquals(Object obj1, Object obj2) know which version to call? I think the only true solution is to make it const and call it a day. -Steve
I fear that you're probably right. The const opEquals must exist, otherwise the object will be unable to use most functions in Phobos. And I don't see how it can safely be synthesised from non-const opEquals. A tiny compromise which could be made, is to silently add 'const' to any class opEquals declaration, even if not present in the source. That way, simple use cases would still compile without complaint. (As long as only a single, precisely defined opEquals signature is permitted, I think any other signature should be flagged as an error -- but it could be converted to the correct signature, instead). Dunno if this would be worth it, though.
Feb 10 2011
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 10 Feb 2011 15:22:48 -0500, Don <nospam nospam.com> wrote:

 A tiny compromise which could be made, is to silently add 'const' to any  
 class opEquals declaration, even if not present in the source. That way,  
 simple use cases would still compile without complaint.
 (As long as only a single, precisely defined opEquals signature is  
 permitted, I think any other signature should be flagged as an error --  
 but it could be converted to the correct signature, instead).

 Dunno if this would be worth it, though.
I think then we get back to http://d.puremagic.com/issues/show_bug.cgi?id=3659 Would it be possible to define a construct that says "you can override this function, but you cannot add any new overloads"? like a precise or something like that? Then we can have a real feature to use for things like this. Sort of like final, but you're not cutting off the virtual function path, just restricting the API to be exactly what you specify. That way someone cannot accidentally create a non-const opEquals and generate a hidden function exception. Although, anyone worth his salt will test the opEquals, which dmd forces through .opEquals(const(Object), const(Object)). -Steve
Feb 10 2011
parent reply Don <nospam nospam.com> writes:
Steven Schveighoffer wrote:
 On Thu, 10 Feb 2011 15:22:48 -0500, Don <nospam nospam.com> wrote:
 
 A tiny compromise which could be made, is to silently add 'const' to 
 any class opEquals declaration, even if not present in the source. 
 That way, simple use cases would still compile without complaint.
 (As long as only a single, precisely defined opEquals signature is 
 permitted, I think any other signature should be flagged as an error 
 -- but it could be converted to the correct signature, instead).

 Dunno if this would be worth it, though.
I think then we get back to http://d.puremagic.com/issues/show_bug.cgi?id=3659
No, we don't, because that issue applies to structs, not classes. For classes, it has to go in the vtable, so the precise signature must be known.
 Would it be possible to define a construct that says "you can override 
 this function, but you cannot add any new overloads"?  like a  precise 
 or something like that?  Then we can have a real feature to use for 
 things like this.  Sort of like final, but you're not cutting off the 
 virtual function path, just restricting the API to be exactly what you 
 specify.  That way someone cannot accidentally create a non-const 
 opEquals and generate a hidden function exception.  Although, anyone 
 worth his salt will test the opEquals, which dmd forces through 
 .opEquals(const(Object), const(Object)).
For sure that could be done. But again -- does it add much value?
Feb 10 2011
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 10 Feb 2011 16:34:57 -0500, Don <nospam nospam.com> wrote:

 Steven Schveighoffer wrote:
 On Thu, 10 Feb 2011 15:22:48 -0500, Don <nospam nospam.com> wrote:

 A tiny compromise which could be made, is to silently add 'const' to  
 any class opEquals declaration, even if not present in the source.  
 That way, simple use cases would still compile without complaint.
 (As long as only a single, precisely defined opEquals signature is  
 permitted, I think any other signature should be flagged as an error  
 -- but it could be converted to the correct signature, instead).

 Dunno if this would be worth it, though.
I think then we get back to http://d.puremagic.com/issues/show_bug.cgi?id=3659
No, we don't, because that issue applies to structs, not classes. For classes, it has to go in the vtable, so the precise signature must be known.
What I meant was, if the compiler complains that some specific function x has the wrong signature, you have assumptions the compiler is making that may not be correct. To me, opEquals is special, but it's still a regular function, and putting extra limitations is sure to ferret out some use case where the limitation is invalid. I consider bool opEquals(int x) const to be a perfectly valid signature in a class.
 Would it be possible to define a construct that says "you can override  
 this function, but you cannot add any new overloads"?  like a  precise  
 or something like that?  Then we can have a real feature to use for  
 things like this.  Sort of like final, but you're not cutting off the  
 virtual function path, just restricting the API to be exactly what you  
 specify.  That way someone cannot accidentally create a non-const  
 opEquals and generate a hidden function exception.  Although, anyone  
 worth his salt will test the opEquals, which dmd forces through  
 .opEquals(const(Object), const(Object)).
For sure that could be done. But again -- does it add much value?
Not really, I don't think it's that big a problem to begin with. If you try comparing two objects and you didn't define your signature right, then you get a hidden func error, since they all go through .opEquals(...) which casts everything to Object (should be const(Object) ). So the only way this gets out is if you never test the code you wrote. -Steve
Feb 10 2011
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday 10 February 2011 12:22:48 Don wrote:
 Steven Schveighoffer wrote:
 On Thu, 10 Feb 2011 10:19:50 -0500, Andrei Alexandrescu
 
 <SeeWebsiteForEmail erdani.org> wrote:
 On 2/10/11 2:58 AM, Peter Alexander wrote:
 On 10/02/11 8:19 AM, Don wrote:
 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on
 everything.
 
 opEquals() is in conflict with this, since it is a member function of
 Object.
 
 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be
 marked
 const.
 (2) If it is not const, then const objects cannot be compared!
 
 Currently, it's not const, but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).
 
 How will we break this dilemma?
It's not possible.
It is, in fact, possible. We can define two opEquals overloads, for const and non-const. By default, the non-const version forwards to the const one.
This doesn't work. If you only define one, then you will have problems with hidden function errors, and/or inconsistencies. For example, how does .opEquals(Object obj1, Object obj2) know which version to call? I think the only true solution is to make it const and call it a day. -Steve
I fear that you're probably right. The const opEquals must exist, otherwise the object will be unable to use most functions in Phobos. And I don't see how it can safely be synthesised from non-const opEquals. A tiny compromise which could be made, is to silently add 'const' to any class opEquals declaration, even if not present in the source. That way, simple use cases would still compile without complaint. (As long as only a single, precisely defined opEquals signature is permitted, I think any other signature should be flagged as an error -- but it could be converted to the correct signature, instead). Dunno if this would be worth it, though.
I'd argue that silently changing function signatures is a _bad_ idea. And when they actually try and do something which would violate the constness of the function, they're going to be left scratching their head as to why a non-const function is getting complaints about const. In general, D has opted to _not_ do things silently. C++ did a fair bit of it in certain places (albeit not with const), and it's caused problems. I don't think that this would be as big a problem, but I still don't think that it's a good idea. Really, while it's reasonable to not force D programmers to use const all over the place, I do _not_ think that it's reasonable for a D programmer to not be aware of const. And since opEquals, opCmp, toHash, and toString/writeTo all need to be const, the programmer is just going to have to deal with that fact. And having to put const on a few functions is likely to cause less pain than figuring out why they're getting errors with regards to const when they never use const anywhere. - Jonathan M Davis
Feb 10 2011
prev sibling parent reply Michel Fortin <michel.fortin michelf.com> writes:
On 2011-02-10 15:22:48 -0500, Don <nospam nospam.com> said:

 Steven Schveighoffer wrote:
 This doesn't work.  If you only define one, then you will have problems 
 with hidden function errors, and/or inconsistencies.  For example, how 
 does .opEquals(Object obj1, Object obj2) know which version to call?
 
 I think the only true solution is to make it const and call it a day.
 
 -Steve
I fear that you're probably right. The const opEquals must exist, otherwise the object will be unable to use most functions in Phobos. And I don't see how it can safely be synthesised from non-const opEquals. A tiny compromise which could be made, is to silently add 'const' to any class opEquals declaration, even if not present in the source. That way, simple use cases would still compile without complaint. (As long as only a single, precisely defined opEquals signature is permitted, I think any other signature should be flagged as an error -- but it could be converted to the correct signature, instead).
I don't like this idea at all. If the problem is that it's too easy to hide the underlying function without noticing (no compile-time error), then fix how the language treats hidden functions (make it a compile-time error). If opEquals has this problem, other non-language-defined functions will have it too, and fixing it with a special case for opEquals will not fix it elsewhere. Not to speak of the inconsistency it adds. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Feb 10 2011
parent reply Don <nospam nospam.com> writes:
Michel Fortin wrote:
 On 2011-02-10 15:22:48 -0500, Don <nospam nospam.com> said:
 
 Steven Schveighoffer wrote:
 This doesn't work.  If you only define one, then you will have 
 problems with hidden function errors, and/or inconsistencies.  For 
 example, how does .opEquals(Object obj1, Object obj2) know which 
 version to call?

 I think the only true solution is to make it const and call it a day.

 -Steve
I fear that you're probably right. The const opEquals must exist, otherwise the object will be unable to use most functions in Phobos. And I don't see how it can safely be synthesised from non-const opEquals. A tiny compromise which could be made, is to silently add 'const' to any class opEquals declaration, even if not present in the source. That way, simple use cases would still compile without complaint. (As long as only a single, precisely defined opEquals signature is permitted, I think any other signature should be flagged as an error -- but it could be converted to the correct signature, instead).
I don't like this idea at all. If the problem is that it's too easy to hide the underlying function without noticing (no compile-time error), then fix how the language treats hidden functions (make it a compile-time error). If opEquals has this problem, other non-language-defined functions will have it too, and fixing it with a special case for opEquals will not fix it elsewhere. Not to speak of the inconsistency it adds.
Oh, I agree. I don't think it's a good idea. It's just the _only_ compromise I could see that would actually work.
Feb 10 2011
parent spir <denis.spir gmail.com> writes:
On 02/11/2011 12:20 AM, Don wrote:
 I don't like this idea at all. If the problem is that it's too easy to hide
 the underlying function without noticing (no compile-time error), then fix
 how the language treats hidden functions (make it a compile-time error). If
 opEquals has this problem, other non-language-defined functions will have it
 too, and fixing it with a special case for opEquals will not fix it
 elsewhere. Not to speak of the inconsistency it adds.
Oh, I agree. I don't think it's a good idea. It's just the _only_ compromise I could see that would actually work.
It seems const is expected to be a rather relevant feature of D2. And to be correct (compared to the notion of const in ...), providing true semantic gain both for programmers and the compiler, IIUC. Then, programmers must learn about it, even if they do not intend to use it (often, or more than they are forced to). FWIW, I have stepped on this issue (incorrect signature of opEquals rejected by the compiler) 3 times in a few weeks. I was rather annoyed the first time, the second time it me let think for a whilet (why is const part of the inherited signature, in fact?), and the third time just copied the signature from the error message, e basta! In fact, I end up finding it good the compiler to loudly protest that way, thus confronting me with the existence of the feature, letting me explore a it a bit even if by myself I wouldn't have done it; this is positive thank to the fact the error also provides the means to correct the bug, using a not to obcure formulation (for once ;-). It is, in any case, far better to give programmers a superficial impression of the compiler getting on their way from time to time (as long as it's rare), than making hidden exceptions to the core semantics of the language just to avoid giving such impressions. Woof, that were more words than I intended to write on the topic, sorry :-) Denis -- _________________ vita es estrany spir.wikidot.com
Feb 10 2011
prev sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 2/10/11 9:33 AM, Steven Schveighoffer wrote:
 On Thu, 10 Feb 2011 10:19:50 -0500, Andrei Alexandrescu
 <SeeWebsiteForEmail erdani.org> wrote:

 On 2/10/11 2:58 AM, Peter Alexander wrote:
 On 10/02/11 8:19 AM, Don wrote:
 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on
 everything.

 opEquals() is in conflict with this, since it is a member function of
 Object.

 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be
 marked
 const.
 (2) If it is not const, then const objects cannot be compared!

 Currently, it's not const, but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).

 How will we break this dilemma?
It's not possible.
It is, in fact, possible. We can define two opEquals overloads, for const and non-const. By default, the non-const version forwards to the const one.
This doesn't work. If you only define one, then you will have problems with hidden function errors, and/or inconsistencies. For example, how does .opEquals(Object obj1, Object obj2) know which version to call?
You define two: const taking a const, and nonconst taking a nonconst.
 I think the only true solution is to make it const and call it a day.

 -Steve
In fact I agree. Doubling the number of basic Object members has other nefarious circumstances. Andrei
Feb 10 2011
prev sibling next sibling parent reply so <so so.so> writes:
 (1) If it is a const member function, then it will have a viral effect  
 on all objects -- any function called by opEquals will have to be marked  
 const.
It doesn't look like we can solve this by switching the constness of an Object.function, unless we also state that every function in Object must be const and accept the fact that no one would use these functions for other purposes like caching, late binding etc... Or maybe changing the design of default class inheritance somehow, like C++, no more single base class, i guess this is out of question :)
Feb 10 2011
next sibling parent reply Don <nospam nospam.com> writes:
so wrote:
 (1) If it is a const member function, then it will have a viral effect 
 on all objects -- any function called by opEquals will have to be 
 marked const.
It doesn't look like we can solve this by switching the constness of an Object.function, unless we also state that every function in Object must be const and accept the fact that no one would use these functions for other purposes like caching, late binding etc...
I can think of many examples where opEquals would be logically pure, but not formally pure, but it's harder to come up with ones where it is not const. I guess you can have situations where a lazily-computed hash value is cached in the object, to speed up comparisons, but it doesn't seem inappropriate for casts to be required when you're doing that. Can you think of a use case where the calling function should know that opEquals() isn't truly const?
Feb 10 2011
next sibling parent so <so so.so> writes:
 Can you think of a use case where the calling function should know that  
 opEquals() isn't truly const?
I can't think of any. If we are going to this way, we need all four of them to be const i think.
Feb 10 2011
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Thursday 10 February 2011 01:36:56 Don wrote:
 so wrote:
 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be
 marked const.
It doesn't look like we can solve this by switching the constness of an Object.function, unless we also state that every function in Object must be const and accept the fact that no one would use these functions for other purposes like caching, late binding etc...
I can think of many examples where opEquals would be logically pure, but not formally pure, but it's harder to come up with ones where it is not const. I guess you can have situations where a lazily-computed hash value is cached in the object, to speed up comparisons, but it doesn't seem inappropriate for casts to be required when you're doing that. Can you think of a use case where the calling function should know that opEquals() isn't truly const?
The hash problem can be semi-reasonably solved by declaring a non-const version of toHash along with the const one. That way, if you're not dealing with const objects, the hash can be cached (whicth the const version could also take advantage of if the non-const version had already been called or if some other function call had calculated the hash previously). However, without logical const, you can't do any sort of caching inside of a const function unless you either break the type system by casting away const or keep the cache outside of the class itself (at which point toHash couldn't be pure, which would suck). So, you're screwed on that point as long as toHash is const, but it _has_ to be const or const and immutable objects can't use it. Actually, that brings up the question as to whether opEquals, opCmp, toHash, and toString/writeTo should be pure as well. It sucks if they're not, but I'm not sure that it's as critical. I think that I'd have to mess around with it a bit to decide how much of a problem that would really be. You can still declare the overridden versions as pure and take advantage of pure as long as you kept the references as being of the derived type, but you'd be screwed if you were doing something that required Object's functions to be pure. Personally, I wish that there was a way to act like a logically pure function was actually pure. You can do that with nothrow by catching any exceptions (and preferably then asserting false if one actually gets thrown), but you can't do that with pure. I'd love it if you could just do the equivalent of that with pure and then call logically pure functions from actually pure ones, and if it isn't actually logically pure and the compiler ends up doing an incorrect optimization, then it's on your head. But it would make it much easier to use pure in situations where you _know_ that a function is logically pure, but you can't mark it as pure (calling C functions would be a prime example). As for opEquals not truly being const... I can't think of any situation where that would be necessary other than trying to save programmers from having to use it, and calling functions _definitely_ care, since they can't use const objects with it if it's not const. So, I'm not quite sure that I understand your question about the calling function needing to know that opEquals isn't truly const. - Jonathan M Davis
Feb 10 2011
prev sibling next sibling parent Michel Fortin <michel.fortin michelf.com> writes:
On 2011-02-10 04:36:56 -0500, Don <nospam nospam.com> said:

 so wrote:
 (1) If it is a const member function, then it will have a viral effect 
 on all objects -- any function called by opEquals will have to be 
 marked const.
It doesn't look like we can solve this by switching the constness of an Object.function, unless we also state that every function in Object must be const and accept the fact that no one would use these functions for other purposes like caching, late binding etc...
I can think of many examples where opEquals would be logically pure, but not formally pure, but it's harder to come up with ones where it is not const. I guess you can have situations where a lazily-computed hash value is cached in the object, to speed up comparisons, but it doesn't seem inappropriate for casts to be required when you're doing that. Can you think of a use case where the calling function should know that opEquals() isn't truly const?
If you use casts on some member variables, make sure the member is marked shared don't forget to cast to shared mutable, not just mutable, because you never know when your immutable object is shared between threads. Suggesting casts looks error prone to me, but it can probably work. To answer the original question, yes opCmp, opEquals, toHash, toString should all be const. Otherwise it makes it impossible to use const/immutable objects in any generic code requiring those functions to be available. And generic code will otherwise work perfectly fine with non-mutable objects once Walter merges my const(Object)ref patch in the mainline. -- Michel Fortin michel.fortin michelf.com http://michelf.com/
Feb 10 2011
prev sibling parent reply Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
On 10/02/2011 09:36, Don wrote:
 so wrote:
 (1) If it is a const member function, then it will have a viral
 effect on all objects -- any function called by opEquals will have to
 be marked const.
It doesn't look like we can solve this by switching the constness of an Object.function, unless we also state that every function in Object must be const and accept the fact that no one would use these functions for other purposes like caching, late binding etc...
I can think of many examples where opEquals would be logically pure, but not formally pure, but it's harder to come up with ones where it is not const. I guess you can have situations where a lazily-computed hash value is cached in the object, to speed up comparisons, but it doesn't seem inappropriate for casts to be required when you're doing that.
Are you sure that such casts should be allowed? The compiler will be expecting opEquals to be proper const, not just logical const, and if it's not the code may break, even if only on some more obscure (but nonetheless valid) cases. The only situation where I see where such cast would be valid, is if the object can ensure all lazily-computed values (well, at least those required for opEquals) have already been computed, and thus executing const opEquals() will indeed not change the object in any way. The good news is that I suspect the fields used for opEquals/opCmp/opHash in any class are unlikely to be fields that are computed lazily. It's just a guess though, anyone have examples otherwise? -- Bruno Medeiros - Software Engineer
Feb 23 2011
parent reply bearophile <bearophileHUGS lycos.com> writes:
Bruno Medeiros:

 The good news is that I suspect the fields used for 
 opEquals/opCmp/opHash in any class are unlikely to be fields that are 
 computed lazily. It's just a guess though, anyone have examples otherwise?
If I have a string type, I'd like to compute its hash value lazily, the first time I need it. Computing it requires time, it's useful for sets and associative arrays, but being the string immutable the result doesn't change, so you want to compute it only once. So only the array of chars is immutable, while the hash is computed lazily and is not immutable (it 's "lazily immutable"). Bye, bearophile
Feb 23 2011
parent Bruno Medeiros <brunodomedeiros+spam com.gmail> writes:
On 23/02/2011 17:50, bearophile wrote:
 Bruno Medeiros:

 The good news is that I suspect the fields used for
 opEquals/opCmp/opHash in any class are unlikely to be fields that are
 computed lazily. It's just a guess though, anyone have examples otherwise?
If I have a string type, I'd like to compute its hash value lazily, the first time I need it. Computing it requires time, it's useful for sets and associative arrays, but being the string immutable the result doesn't change, so you want to compute it only once. So only the array of chars is immutable, while the hash is computed lazily and is not immutable (it 's "lazily immutable"). Bye, bearophile
(Hum, I was thinking more of the inputs to the functions, the data that is primarily used to determine the equality/comparison.) But that's a completely good and valid example nonetheless, in that it would not be uncommon for opHash to have some values computed lazily. But what about the other functions, opEquals/opCmp, any examples? -- Bruno Medeiros - Software Engineer
Feb 25 2011
prev sibling parent spir <denis.spir gmail.com> writes:
On 02/10/2011 10:09 AM, so wrote:
 (1) If it is a const member function, then it will have a viral effect on all
 objects -- any function called by opEquals will have to be marked const.
It doesn't look like we can solve this by switching the constness of an Object.function,
Is this point very annoying in practice? In my experience, such "language methods" like opEquals are typically small and simple. They do not call other funcs very often, except sometimes super(), or the opEquals of their members. When they call one 'normal' func, it can be one that barely has any other use than beeing called from there. The remaining cases of virality should be very rare, shouldn't they? Globally, I wouldn't care very much about the viral effect of const for such language methods, precisely. What do you think? denis -- _________________ vita es estrany spir.wikidot.com
Feb 10 2011
prev sibling next sibling parent reply Jason House <jason.james.house gmail.com> writes:
Don Wrote:

 Andrei once stated a worthy goal: as far as possible, const should be 
 opt-in: it should be possible to code without requiring const on everything.
 
 opEquals() is in conflict with this, since it is a member function of 
 Object.
 
 (1) If it is a const member function, then it will have a viral effect 
 on all objects -- any function called by opEquals will have to be marked 
 const.
 (2) If it is not const, then const objects cannot be compared!
 
 Currently, it's not const,  but the problem isn't visible because of 
 compiler bug 5080. (Same problem applies to opCmp).
 
 How will we break this dilemma?
class LazyObject{ bool OpEquals(LazyObject); } class Object : LazyObject{ override bool OpEquals(LazyObject) const; bool opEquals(const Object) const; } By default, all classes derive from Object, but those that want to ignore viral const or implement lazy calculations can derive from LazyObject.
Feb 10 2011
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 10 Feb 2011 09:15:36 -0500, Jason House  
<jason.james.house gmail.com> wrote:

 Don Wrote:

 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on  
 everything.

 opEquals() is in conflict with this, since it is a member function of
 Object.

 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be marked
 const.
 (2) If it is not const, then const objects cannot be compared!

 Currently, it's not const,  but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).

 How will we break this dilemma?
class LazyObject{ bool OpEquals(LazyObject); } class Object : LazyObject{ override bool OpEquals(LazyObject) const; bool opEquals(const Object) const; } By default, all classes derive from Object, but those that want to ignore viral const or implement lazy calculations can derive from LazyObject.
This doesn't work. What if the user overrides one and not the other? Or the semantics are different? Then const objects compare differently than non-const ones. -Steve
Feb 10 2011
parent reply Jason House <jason.james.house gmail.com> writes:
Steven Schveighoffer Wrote:

 On Thu, 10 Feb 2011 09:15:36 -0500, Jason House  
 <jason.james.house gmail.com> wrote:
 
 Don Wrote:

 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on  
 everything.

 opEquals() is in conflict with this, since it is a member function of
 Object.

 (1) If it is a const member function, then it will have a viral effect
 on all objects -- any function called by opEquals will have to be marked
 const.
 (2) If it is not const, then const objects cannot be compared!

 Currently, it's not const,  but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).

 How will we break this dilemma?
class LazyObject{ bool OpEquals(LazyObject); } class Object : LazyObject{ override bool OpEquals(LazyObject) const; bool opEquals(const Object) const; } By default, all classes derive from Object, but those that want to ignore viral const or implement lazy calculations can derive from LazyObject.
This doesn't work. What if the user overrides one and not the other? Or the semantics are different? Then const objects compare differently than non-const ones. -Steve
You can never stop programmers from writing incorrect code... The way I envision this, "bool opEquals(LazyObject) const" would rarely be overridden. I would recommend writing it in terms of the other function. Something akin to the following: bool OpEquals(LazyObject o) const{ auto x = cast(const Object) o; if (x is null) return false; return (this==x); } There may be a better way to implement that, but the general idea is that 99% of people overriding a class's OpEquals will return false for anything that is not an Object. I don't know D's overloading rules well enough... Which overload would be called for the following code? Object o1; Object o2; o1 == o2; Ideally, it'd call the const(Object) overload and not the LazyObject overload. I have a sneaking suspicion that D would consider it ambiguous and give a compile error. I'm sure that could be fixed if people like this solution.
Feb 10 2011
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 10 Feb 2011 12:24:02 -0500, Jason House  
<jason.james.house gmail.com> wrote:

 Steven Schveighoffer Wrote:

 On Thu, 10 Feb 2011 09:15:36 -0500, Jason House
 <jason.james.house gmail.com> wrote:

 Don Wrote:

 Andrei once stated a worthy goal: as far as possible, const should be
 opt-in: it should be possible to code without requiring const on
 everything.

 opEquals() is in conflict with this, since it is a member function of
 Object.

 (1) If it is a const member function, then it will have a viral  
effect
 on all objects -- any function called by opEquals will have to be  
marked
 const.
 (2) If it is not const, then const objects cannot be compared!

 Currently, it's not const,  but the problem isn't visible because of
 compiler bug 5080. (Same problem applies to opCmp).

 How will we break this dilemma?
class LazyObject{ bool OpEquals(LazyObject); } class Object : LazyObject{ override bool OpEquals(LazyObject) const; bool opEquals(const Object) const; } By default, all classes derive from Object, but those that want to ignore viral const or implement lazy calculations can derive from LazyObject.
This doesn't work. What if the user overrides one and not the other? Or the semantics are different? Then const objects compare differently than non-const ones. -Steve
You can never stop programmers from writing incorrect code...
No but you can make things easier to avoid writing incorrect code. This kind of change invites mistakes, because it looks like you have to completely duplicate your logic, and forgetting to modify one and not the other happens to even seasoned veterans.
 The way I envision this, "bool opEquals(LazyObject) const" would rarely  
 be overridden. I would recommend writing it in terms of the other  
 function. Something akin to the following:
 bool OpEquals(LazyObject o) const{
   auto x = cast(const Object) o;
   if (x is null)
     return false;
   return (this==x);
 }
This also doesn't work without extra effort -- overriding less than the full overload set results in runtime hidden function errors. You need to alias the base class' method, in *every* derived class. I don't really think adding extra complexity makes things better. I think a single overload for opEquals (which is const) is fine. Anything beyond that can use a separate method name, and you just call the method directly. -Steve
Feb 10 2011
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 10 Feb 2011 13:59:24 -0500, Steven Schveighoffer  
<schveiguy yahoo.com> wrote:
 No but you can make things easier to avoid writing incorrect code.  This  
 kind of change invites mistakes, because it looks like you have to  
 completely duplicate your logic, and forgetting to modify one and not  
 the other happens to even seasoned veterans.
This should have said "forgetting to modify one when you modify the other" -Steve
Feb 10 2011
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Friday 11 February 2011 02:43:11 spir wrote:
 On 02/11/2011 07:13 AM, Jonathan M Davis wrote:
 We _must_ have it there, so anyone overriding those functions _must_
 
  >  use it for those functions. They could create non-const versions
  >  in addition to
  >  the const ones,
It is the whole point, they can't.
Hmm. You're right (I just tried it).
In the worst case, people who really, really, want it it non-const are left with: struct S { ... bool equals (ref S s) {...} } ... if (s1.equals(s2)) {...} I do not find it /that/ terrible (esp.compared to many other wrokarounds we commonly are forced to use for various reasons). The one bad case is if S belongs to the interface to client code. In other words, this is not an acceptable solution for library public types. But since const opEquals (and same reasoning for other methods) is only annoying for structs that (1) need custom equality predicate (2) call other funcs, else as member opEquals, (3) which themselves may be called by yet other funcs (else virality does not spread), then I guess very few cases remain. What do you think?
structs are a different beast. The issue under discussion here is classes where polymorphism gets involved. There, the function signatures have to match. Now, structs are still problematic, because the compiler currently insists that their signature for opEquals look something like bool opEquals(const ref S s) const; Structs shouldn't be so picky. Pretty much anything named opEquals which takes one argument and returns bool should work. But that hasn't been fixed yet. Regardless, it's a separate issue from classes where polymorphism puts much stricter requirements on opEquals' signature. - Jonathan M Davis
Feb 11 2011