March 18, 2013
On Monday, 18 March 2013 at 01:05:25 UTC, Jonathan M Davis wrote:
> On Monday, March 18, 2013 00:53:52 Stewart Gordon wrote:
>> Why would some class want to implement these methods in a way that alters
>> the object?
>
> Because const in D is physical const, not logical const. So, for instance,
> const prevents caching. And it's quite possible that a type which really cared
> about efficiency would cache the calculated value for toHash. Make toHash const
> would make that impossible. Another possible problem would be lazy
> initialization. If opEquals is const, then lazy initialization becomes
> impossible.
>
> We've discussed this on a number of occasions, and it's clear that forcing
> these functions to be const is a major problem, and yet they do need to be
> const for them to work with const objects. What was finally decided during the
> last big discussion on this a few months back was that we would remove
> opEqulas, opCmp, toHash, and toString from Object. They don't need to be
> there. As long as everything in the runtime which deals with them is
> templated, then there's no technical reason why Object would need them. D
> isn't Java where we have containers of Object or anything like that. Putting
> them on Object just restricts us.
>

Even on Java's case and as extent .NET they aren't strictly necessary.

Nowadays I would say it was a bad design decision and the best way would have been to have created interfaces for those operations. The only benefit would be default implementations.

But I imagine the language designers, like everyone else, had to build their knowledge about classes vs interfaces, and many other OO abstractions, so what now seems wrong was seen as right at the time.

I remember thinking Object was an evolution over C++ way of doing things.

--
Paulo
March 18, 2013
On 18/03/2013 01:05, Jonathan M Davis wrote:
> On Monday, March 18, 2013 00:53:52 Stewart Gordon wrote:
>> Why would some class want to implement these methods in a way that alters
>> the object?
>
> Because const in D is physical const, not logical const. So, for instance,
> const prevents caching. And it's quite possible that a type which really cared
> about efficiency would cache the calculated value for toHash. Make toHash const
> would make that impossible. Another possible problem would be lazy
> initialization. If opEquals is const, then lazy initialization becomes
> impossible.

Look up std.functional.memoize.

> We've discussed this on a number of occasions, and it's clear that forcing
> these functions to be const is a major problem, and yet they do need to be
> const for them to work with const objects. What was finally decided during the
> last big discussion on this a few months back was that we would remove
> opEqulas, opCmp, toHash, and toString from Object. They don't need to be
> there. As long as everything in the runtime which deals with them is
> templated, then there's no technical reason why Object would need them.
<snip>

That's true.  It would even grant the benefit of being able to use opEquals, opCmp, toHash or some combination of them in the AA implementation, depending on which methods exist.

But the drawback of this approach (compared with making Object const-correct) is that some library programmers will (continue to) neglect const-correctness when implementing these methods, just as Walter did.  Either by simply forgetting to declare them const, by caching information wihtin the object instead of using memoize, or by inadvertently doing something else that changes the object's state.

Stewart.
March 18, 2013
On 03/18/2013 11:11 PM, Stewart Gordon wrote:
> ...
>
> But the drawback of this approach (compared with making Object
> const-correct) is that some library programmers will (continue to)
> neglect const-correctness

That's a C++ term and it is not applicable to D.

March 18, 2013
On Monday, March 18, 2013 23:53:05 Timon Gehr wrote:
> On 03/18/2013 11:11 PM, Stewart Gordon wrote:
> > ...
> > 
> > But the drawback of this approach (compared with making Object
> > const-correct) is that some library programmers will (continue to)
> > neglect const-correctness
> 
> That's a C++ term and it is not applicable to D.

I wouldn't go that far. I think that the term is still quite applicable to D, but given how much stricter D's const is, the situation is certainly quite different with regards to when it does and doesn't make sense to use const. But it's still best to make things const whenever they can be, which is the whole idea behind const-correctness. It's just that that happens less often in D than it does in C++.

- Jonathan M Davis
March 18, 2013
On Monday, March 18, 2013 22:11:49 Stewart Gordon wrote:
> On 18/03/2013 01:05, Jonathan M Davis wrote:
> > On Monday, March 18, 2013 00:53:52 Stewart Gordon wrote:
> >> Why would some class want to implement these methods in a way that alters the object?
> > 
> > Because const in D is physical const, not logical const. So, for instance, const prevents caching. And it's quite possible that a type which really cared about efficiency would cache the calculated value for toHash. Make toHash const would make that impossible. Another possible problem would be lazy initialization. If opEquals is const, then lazy initialization becomes impossible.
> 
> Look up std.functional.memoize.

It doesn't work with pure as it forces you to put state outside of the object, and it's only applicable to caching, not lazy initialization. In either case, by making opEquals const, it's impossible to have any state cached in the object or to have any state in the object which is lazily initialized.

> > We've discussed this on a number of occasions, and it's clear that forcing these functions to be const is a major problem, and yet they do need to be const for them to work with const objects. What was finally decided during the last big discussion on this a few months back was that we would remove opEqulas, opCmp, toHash, and toString from Object. They don't need to be there. As long as everything in the runtime which deals with them is templated, then there's no technical reason why Object would need them.
> <snip>
> 
> That's true. It would even grant the benefit of being able to use opEquals, opCmp, toHash or some combination of them in the AA implementation, depending on which methods exist.
> 
> But the drawback of this approach (compared with making Object const-correct) is that some library programmers will (continue to) neglect const-correctness when implementing these methods, just as Walter did. Either by simply forgetting to declare them const, by caching information wihtin the object instead of using memoize, or by inadvertently doing something else that changes the object's state.

structs don't have any restrictions on them either with regards to opEquals and friends, and that works just fine. Given how strict D's const is, it just plain doesn't work to force it on people. Yes, it's great to use it as much as you reasonably can, but unlike C++, const comes at a very high cost in D, and it must be used with care. You just can't assume that it'll work in the general case.

- Jonathan M Davis
March 19, 2013
On Monday, 18 March 2013 at 01:05:25 UTC, Jonathan M Davis wrote:
> So, once all of those functions are removed from Object, derived types can
> then define them with whatever attributes they want. The only thing you lose is
> the ability to compare Objects directly, which is not necessary in D and is
> arguably a bad idea anyway.

It's worth noting that including a standard interface (as in the interface keyword) for stuff like Comparable, Hashable etc. is a possibility that can be explored to enable runtime polymorphism akin to the current Object for programs that need it. Either way, I'm glad it's on its way out of Object.
March 19, 2013
On 18/03/2013 23:06, Jonathan M Davis wrote:
> On Monday, March 18, 2013 22:11:49 Stewart Gordon wrote:
<snip>
>> Look up std.functional.memoize.
>
> It doesn't work with pure as it forces you to put state outside of the object,
> and it's only applicable to caching, not lazy initialization.

Why can't it be used as a means of lazy initialization?

> In either case,
> by making opEquals const, it's impossible to have any state cached in the
> object or to have any state in the object which is lazily initialized.
<snip>

But that equally doesn't work with pure.

But why does the state need to be physically in the object, anyway?  The whole point of memoize is that it gets around the problem by storing the state outside the object.

Moreover, memoize can be made to work with pure by having something like @trusted that overrides purity instead of safety.  Or by going the whole hog and building it into the language.


In any case, if you find yourself having to prevent const objects from being compared for equality or whatever, then you need to rethink your strategy.  Or at the very least, define a const version as well.  By having the const version in Object, it doesn't prevent someone from additionally defining a non-const version that speeds up subsequent calculations by storing something extra in the object.  This could possibly be used by the const version as well.

Stewart.
March 19, 2013
On 19/03/2013 00:19, Jakob Ovrum wrote:
<snip>
> It's worth noting that including a standard interface (as in the
> interface keyword) for stuff like Comparable, Hashable etc. is a
> possibility that can be explored to enable runtime polymorphism akin to
> the current Object for programs that need it. Either way, I'm glad it's
> on its way out of Object.

Can you think a use case that can't be fulfilled by using an IsExpression to test whether a type supports such an operation?

Stewart.
March 19, 2013
On Tuesday, March 19, 2013 00:26:40 Stewart Gordon wrote:
> On 18/03/2013 23:06, Jonathan M Davis wrote:
> > On Monday, March 18, 2013 22:11:49 Stewart Gordon wrote:
> <snip>
> 
> >> Look up std.functional.memoize.
> > 
> > It doesn't work with pure as it forces you to put state outside of the object, and it's only applicable to caching, not lazy initialization.
> 
> Why can't it be used as a means of lazy initialization?

Because if one of the member variables hasn't been initialized yet, then it can't be compared.

> In any case, if you find yourself having to prevent const objects from being compared for equality or whatever, then you need to rethink your strategy. Or at the very least, define a const version as well. By having the const version in Object, it doesn't prevent someone from additionally defining a non-const version that speeds up subsequent calculations by storing something extra in the object. This could possibly be used by the const version as well.

There _are_ types which _can't_ have const on opEquals, because they _need_ to be able to mutate state in opEquals (e.g a lazily initialized object). They may very well be logically const, but they're not physically const.

Even if it's generally best practice for opEquals to be const, it's needless restrictive to require it. D is a systems language, and needs to be flexible and pragmatic. We do _not_ want to force const on people. D's const is far too restrictive to require it.

It was already agreed upon by Walter and Andrei that we would get rid of opEquals, opCmp, toHash, and toString on Object. It's just a question of doing what needs to be done to make that possible and providing an appropriate transition path.

- Jonathan M Davis
March 19, 2013
On Monday, 18 March 2013 at 01:05:25 UTC, Jonathan M Davis wrote:
> On Monday, March 18, 2013 00:53:52 Stewart Gordon wrote:
>> Why would some class want to implement these methods in a way that alters
>> the object?
>
> Because const in D is physical const, not logical const. So, for instance,
> const prevents caching. And it's quite possible that a type which really cared
> about efficiency would cache the calculated value for toHash. Make toHash const
> would make that impossible. Another possible problem would be lazy
> initialization. If opEquals is const, then lazy initialization becomes
> impossible.
>
> We've discussed this on a number of occasions, and it's clear that forcing
> these functions to be const is a major problem, and yet they do need to be
> const for them to work with const objects. What was finally decided during the
> last big discussion on this a few months back was that we would remove
> opEqulas, opCmp, toHash, and toString from Object. They don't need to be
> there. As long as everything in the runtime which deals with them is
> templated, then there's no technical reason why Object would need them. D
> isn't Java where we have containers of Object or anything like that. Putting
> them on Object just restricts us.
>
> So, once all of those functions are removed from Object, derived types can
> then define them with whatever attributes they want. The only thing you lose is
> the ability to compare Objects directly, which is not necessary in D and is
> arguably a bad idea anyway.
>
> The work on this conversion hasn't been done yet, and a transition plan will
> have to be put in place to minimize code breakage, but that's what was decided
> on as the solution to the issues with const and Object's functions.
>
> - Jonathan M Davis

Am I right in thinking that removal of these methods from Object will mean that it will no longer be necessary for the the argument to be of type Object and that the need for casting in the implementation will go away?

Peter