July 11, 2012
On Wednesday, 11 July 2012 at 02:55:33 UTC, Jakob Ovrum wrote:
> On Wednesday, 11 July 2012 at 02:33:53 UTC, Jonathan M Davis wrote:
>> -snip-
>>
>> So, with that, we can have const work wonderfully without requiring it, even
>> if it does take a bit of work to get around it with classes. So, what am I
>> missing here? Why doesn't this work? Or has Walter just not properly
>> considered this option?
>>
>> - Jonathan M Davis
>
> This is exactly the kind of balance I am hoping we can implement. I think Hara Kenji suggested something very similar at one point, but it had some kind of problem with it. It would be awesome if Kenji could provide some insight on that. I think his proposal is somewhere on Github, I'll have a look.

The relevant discussion containing the proposal is here:

    https://github.com/D-Programming-Language/phobos/pull/262

I was wrong; I don't see anyone having found any flaws with the final proposal.

And while the proposal wasn't explicitly accepted nor rejected, it doesn't look like it found its way into the final pull request:

    https://github.com/D-Programming-Language/druntime/pull/72


July 11, 2012
On Wed, Jul 11, 2012 at 04:59:28AM +0200, Jakob Ovrum wrote:
> On Wednesday, 11 July 2012 at 02:02:52 UTC, Andrei Alexandrescu wrote:
> >On 7/10/12 9:45 PM, Timon Gehr wrote:
> >>I do not desire logical const as a language feature. But conservative type systems are not good for everything. The root of the class hierarchy needs to be good for everything. Object is not an adequate root any more.
> >
> >How about we consider just stiffening that upper lip and implement comparison and hashing without modifying their target?
> >
> >Andrei
> 
> It's more likely to go down like this: programmer attempts to write his opEquals (or toString etc) within the restrictions of const, but fails due to the requirements of the implementation (which can easily go beyond simple performance measures like caching, as demonstrated). The programmer then writes his own mutable member function and neglects opEquals altogether. If the programmer is real nice, he/she will write a throwing opEquals stub.

This is exactly what I was saying. All that beautiful, pristine, perfect infrastructure we're building in druntime eventually just gets sidestepped, because it is unable to cater for what the programmer needs, and so the programmer ends up reimplementing his own infrastructure, over and over again. I can't see how that is beneficial.


T

-- 
What doesn't kill me makes me stranger.
July 11, 2012
On 10/07/12 19:13, H. S. Teoh wrote:
> On Tue, Jul 10, 2012 at 06:48:51PM +0200, Timon Gehr wrote:
>> On 07/10/2012 06:45 PM, H. S. Teoh wrote:
>>> Yeah, this is logical const. Unfortunately, D doesn't have logical
>>> const.
>>>
>>
>> Then why on earth is druntime acting as if it does?
>
> Y'know, this brings up an interesting question. Do methods like toString
> _need_ to be const? That is, _physical_ const?  Or are we unconsciously
> conflating physical const with logical const here?
>
> Yes, certain runtime operations need to be able to work with const
> methods, but I wonder if those required const methods really belong to a
> core set of more primitive operations that guarantee physical const, and
> perhaps shouldn't be conflated with logical operations like "convert
> this object to a string representation", which _may_ require caching,
> etc.?
>
> Or perhaps application code want to be defining their own non-const
> versions of certain methods so that they can do whatever they need to do
> with logical const, without worrying about breaking physical const-ness.
>
> I'm starting to think that D's hardline approach to const is clashing
> with the principle of information hiding. Users of a class shouldn't
> _need_ to know if an object is caching the value of toString, toHash, or
> whatever it is. What they care for is that the object doesn't visibly
> change, that is, logical const. Binary const implies logical const, but
> the implication doesn't work the other way round. While it's nice to
> have binary const (strong, enforceable guarantee), it breaks
> encapsulation: just because a class needs to do caching, means its
> methods can't be const, and this is a visible (and viral, no less)
> change in its external API. What should just be an implementation detail
> has become a visible difference to the outside world -- encapsulation is
> broken.
>
> I don't know how to remedy this. It's clear that physical const does
> have its value -- it's necessary to properly support immutable, allows
> putting data in ROM, etc.. But it's also clear that something is missing
> from the picture. Implementation details are leaking past object APIs,
> caching and other abstractions can't work with const, etc., and that's
> not a good thing.
>
>
> T

I think you're right.
Something I wonder about, though, is how many different use cases are we dealing with?

Suppose we had a caching solution (you could think of it as @cached, but it could be done in a library). The user would need to provide a const, pure function which returns the same value that is stored in the cache.
This is enforceable. The only way to write to the cache, is by calling the function.

How far would that take us? I don't think there are many use cases for logically pure, apart from caching, but I have very little idea about logical const.




July 11, 2012
On 2012-07-11 02:03, Walter Bright wrote:

> So do I. But all language design involves tradeoffs. I believe the
> advantages of const toHash etc. outweigh the disadvantage of less
> straightforward memoization.

Can't there be non-const versions of these methods as well?

-- 
/Jacob Carlborg
July 11, 2012
On 2012-07-11 02:20, Timon Gehr wrote:

> I for one would be satisfied if inheriting from object became optional:
>
> // object.di
> class RawObject /+ this is the root of the class hierarchy +/{ }
> class SynchronizableObject : RawObject { void* monitor; }
> class Object : SynchronizableObject {
> const { stuff }
> }
>
> // user code
> class NoCruft : RawObject {
> // ...
> }
>

Ruby 1.9 is doing something similar.

class BasicObject
end

class Object < BasicObject
end

class Foo # inherits from Object
end

class Bar < BasicObject # inherits from BasicObject
end

I wonder if the same can be done, with the now base class protection, in D:

class RawObject : private Object
{}

class Foo : RawObject
{}

-- 
/Jacob Carlborg
July 11, 2012
Andrei Alexandrescu , dans le message (digitalmars.D:171828), a écrit :
> On 7/10/12 5:19 PM, H. S. Teoh wrote:
> 
> There is value in immutable objects that has been well discussed, which is incompatible with logical constness. We can change the language such as: a given type X has the option to declare "I want logical const and for that I'm giving up the possibility of creating immutable(X)". That keeps things proper for everybody - immutable still has the strong properties we know and love, and such types can actually use logical const.
> 
> A number of refinements are as always possible.

I think this is a good idea, but for classes, inheritance is an issue.

Example:

class A
{
  int a;
  int compute() const pure { return a; }
  final int fun() const pure
  {
    a.compute; // optimised out by the compiler
    return a.compute;
  }
}

class B : A
{
  @mutable int b;
  override int compute() const pure
  {
     if(!b) b = longComputation(a);
     // a += 1; // error, a is bitwise-const
     return b; // mutating and returning a mutable part at the
               // programmer's risk
  }
}

A.compute is bitwise const. However B.compute is logical const. A.fun is bitwise const, and can be optimised. But that is no longer true with a B instance. However, the compiler must be able to make those optimizations, otherwise all the power of const for any non final object is lost, because someone may derive a logical const class. This means the programmer is *responsible* for creating a logical const-behavior. This is a serious issue.

Given the system-programming aspect of D, I would say the programmer should be allowed to do such a thing, taking the risk to have an undefined behavior. But with great caution. At least, it will be less dangerous than casting away const. Just providing a way to make it impossible to create an immutable instance of some classes would make it less dangerous to cast away constness.

-- 
Christophe
July 11, 2012
On 7/10/12 10:22 PM, Walter Bright wrote:
> On 7/10/2012 6:53 PM, Andrei Alexandrescu wrote:
>> On 7/10/12 9:14 PM, Walter Bright wrote:
>>> Anyhow, the point of @trusted is to notify the maintainer that "here be
>>> dragons".
>>
>> I think that's not representing @trusted quite accurately. There's no
>> dragon
>> there. @trusted means "the code is correct but not mechanically
>> checkable".
>> Casting away const is NOT correct.
>
> You're right in that it would break immutable args passed.
>
> PIMPL is a better option.

I am pretty sure your approach with PIMPL is also undefined, so we may as well stop suggesting it as a viable possibility.

Consider:

immutable(T) t1 = create();
int x = t1.x;
const(T) t2 = t2;
t2.method();
assert(x == t1.x); // the compiler should be able to assume this

For the entire immutable thing to work, the compiler must have an iron-clad guarantee that transitively-accessed data members in t1 will preserve before and after the call to t2.method(). Your proposed cast, if ever defined, essentially breaks that guarantee. If that is down, the entire notion of immutable breaks down.

Please do not propose pimpl and cast anymore. This is important. Thanks.


Andrei
July 11, 2012
On 7/10/12 10:33 PM, Jonathan M Davis wrote:
> Yeah. It seems to me that a reasonable approach would be to give Object two
> versions of opEquals, opCmp, toString, and toHash - a const and non-const
> version of each. The non-const version then calls the const version. So, the
> normal thing to do is have your class work with const and override the const
> version. If the non-const version on Object gets called, then the const
> version in the derived class gets called, and if the derived type is used
> directly, then it'll always use the const version, because that's what the
> derived type has.
>
> On the other hand, for a class which doesn't work with const, it does this:
>
> 1. Override the non-const versions of opEquals, opCmp, toString, and toHash,
> giving them the implementations which you want.
>
> 2. Override the const versions of opEquals, opCmp, toString, and toHash and
> make their bodies assert(0).
>
> That way, those 4 functions can be used normally as long as the object isn't
> assigned to a const reference, and if it is, then you get an Error.

I was a long-time proponent of this. It's less exciting than it may seem actually.

(a) Classes that work with const just fine incur one extra virtual call. I think this can be avoided by having the compiler plant the same pointer for the const and non-const version in the vtable.

(b) Classes that can't do as little as one of these four operations without mutating the object are completely excluded from the immutability system, even if they'd otherwise benefit from it. Even those that don't "care" they need to actively _work_ on not caring, which doesn't sit well.

So I don't see this as a viable solution to people who are fine with const, but would like to use e.g. some lazy computation.


Andrei
July 11, 2012
On 7/10/12 10:59 PM, Jakob Ovrum wrote:
> On Wednesday, 11 July 2012 at 02:02:52 UTC, Andrei Alexandrescu wrote:
>> On 7/10/12 9:45 PM, Timon Gehr wrote:
>>> I do not desire logical const as a language feature. But conservative
>>> type systems are not good for everything. The root of the class
>>> hierarchy needs to be good for everything. Object is not an adequate
>>> root any more.
>>
>> How about we consider just stiffening that upper lip and implement
>> comparison and hashing without modifying their target?
>>
>> Andrei
>
> It's more likely to go down like this: programmer attempts to write his
> opEquals (or toString etc) within the restrictions of const, but fails
> due to the requirements of the implementation (which can easily go
> beyond simple performance measures like caching, as demonstrated). The
> programmer then writes his own mutable member function and neglects
> opEquals altogether. If the programmer is real nice, he/she will write a
> throwing opEquals stub.

I gave evidence on a large, high quality C++ codebase that the use of mutable (which is the solution of choice for memoization, caching, and lazy computation) is extremely scarce.

What evidence do you have for your prediction?


Andrei
July 11, 2012
On 7/11/12 12:59 AM, H. S. Teoh wrote:
> On Wed, Jul 11, 2012 at 04:59:28AM +0200, Jakob Ovrum wrote:
>> On Wednesday, 11 July 2012 at 02:02:52 UTC, Andrei Alexandrescu
>> wrote:
>>> On 7/10/12 9:45 PM, Timon Gehr wrote:
>>>> I do not desire logical const as a language feature. But
>>>> conservative type systems are not good for everything. The root of
>>>> the class hierarchy needs to be good for everything. Object is not
>>>> an adequate root any more.
>>>
>>> How about we consider just stiffening that upper lip and implement
>>> comparison and hashing without modifying their target?
>>>
>>> Andrei
>>
>> It's more likely to go down like this: programmer attempts to write
>> his opEquals (or toString etc) within the restrictions of const, but
>> fails due to the requirements of the implementation (which can
>> easily go beyond simple performance measures like caching, as
>> demonstrated). The programmer then writes his own mutable member
>> function and neglects opEquals altogether. If the programmer is real
>> nice, he/she will write a throwing opEquals stub.
>
> This is exactly what I was saying. All that beautiful, pristine, perfect
> infrastructure we're building in druntime eventually just gets
> sidestepped, because it is unable to cater for what the programmer
> needs, and so the programmer ends up reimplementing his own
> infrastructure, over and over again. I can't see how that is beneficial.

How often do you need memoization? It's not even recognized by this mailer's editor.

Andrei