July 11, 2012 Re: Inherited const when you need to mutate | ||||
---|---|---|---|---|
| ||||
Posted in reply to H. S. Teoh | On 7/10/12 5:19 PM, H. S. Teoh wrote:
> Isn't there something we can do to improve this situation?
There is, only thing is there's no easy way (without adding some amount of complication to the language).
Generally const(T) is a supertype of T and immutable(T), meaning it could originate from either.
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'm not sure if I was very clear. In brief, logical constness breaks things only if the original object was immutable.
Andrei
|
July 11, 2012 Re: Congratulations to the D Team! | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | On Wednesday, 11 July 2012 at 01:19:16 UTC, Andrei Alexandrescu wrote:
> On 7/10/12 6:05 PM, Walter Bright wrote:
>> A straightforward workaround is to use PIMPL to encapsulate the logical
>> const stuff, and then cast the reference to const to use inside the
>> opEquals.
>
> s/straightforward/awful/
>
> Andrei
Honestly, I think the ideal would be to give people the alternative of having a mutable opEquals etc. These methods only need to be logically constant (of course, they don't *need* to be even that, to the joy of operator overloading abusers worldwide), which means no help from the compiler, as it should be - just plain mutable, with the programmer providing the guarantee.
There was work in this direction but I understand it was ripe with issues of its own, however I don't understand how any other path could even be considered when it's just moving from one end of the scale to the other.
Yes, Object *needs* to work with const, this is imperative. But does it need to compromise on implementations relying on mutability? I was hoping the answer was "no". Obviously such an override would not work with const references, but some classes really don't lean themselves to immutability at all, alleviating the need for const. I can also imagine that if both were allowed, someone would leverage the ability to use caching in the mutable overload, then falling back to eager computation in the const overload.
|
July 11, 2012 Re: Congratulations to the D Team! | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | On Wednesday, 11 July 2012 at 01:19:16 UTC, Andrei Alexandrescu wrote:
> On 7/10/12 6:05 PM, Walter Bright wrote:
>> A straightforward workaround is to use PIMPL to encapsulate the logical
>> const stuff, and then cast the reference to const to use inside the
>> opEquals.
>
> s/straightforward/awful/
>
> Andrei
Honestly, I think the ideal would be to give people the alternative of having a mutable opEquals etc. These methods only need to be logically constant (of course, they don't *need* to be even that, to the joy of operator overloading abusers worldwide), which means no help from the compiler, as it should be - just plain mutable, with the programmer providing the guarantee.
There was work in this direction but I understand it was ripe with issues of its own, however I don't understand how any other path could even be considered when it's just moving from one end of the scale to the other.
Yes, Object *needs* to work with const, this is imperative. But does it need to compromise on implementations relying on mutability? I was hoping the answer was "no". Obviously such an override would not work with const references, but some classes really don't lean themselves to immutability at all, alleviating the need for const. I can also imagine that if both were allowed, someone would leverage the ability to use caching in the mutable overload, then falling back to eager computation in the const overload.
|
July 11, 2012 Re: Inherited const when you need to mutate | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 7/10/12 6:39 PM, Walter Bright wrote:
> On 7/10/2012 3:13 PM, H. S. Teoh wrote:
>> I don't think they were rushed. There's been a push for making druntime
>> and Phobos const-correct for a while now. I don't think this change is a
>> _mistake_ per se, but it does expose a flaw in the language: const is
>> too limited in scope, and we need something else to fill in for use
>> cases where const isn't good enough.
>
>
> There's a gigantic problem with logical const. It is completely
> unenforceable - it is documentation only. All the reasoning and
> mechanical guarantees that come with const, immutable, and purity fall
> apart.
I think we need to unlock that mindset lest we risk short-circuiting all reason by always (constantly, heh) falling back on dogma a la "logical const is unenforceable". This is a very damaging pattern.
We can devise a number of solutions in which there's lazy computation inside const methods, while still preserving the current guarantees. The only issue with those is they complicate the definition and implementation of D.
So the question remains whether the complication is worth it. I personally am not convinced. Sure, examples can be found if one looks real hard, but I think their importance is overstated when the community gets in the mood of proving their need. Fact is, the use of "mutable" in C++ is very scant even in codebases that use const religiously (like ours). A cursory search yielded 0.01% as many uses of "mutable" as "const" in our very large codebase.
Andrei
|
July 11, 2012 Re: Congratulations to the D Team! | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 7/10/12 7:10 PM, Walter Bright wrote:
> PIMPL means pointer to implementation. Your const struct is just a
> wrapper around a pointer, and that pointer can be cast to mutable before
> calling member functions on it.
>
> Or:
>
> bool opEquals(const Object p) const
> {
> Object q = cast() p;
> Object r = cast() this;
> return r.non_const_equals(q);
> }
>
> Of course, you'd have to make this @trusted.
s/make this @trusted/never use this/
Andrei
|
July 11, 2012 Re: Congratulations to the D Team! | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 07/11/2012 03:14 AM, Walter Bright wrote: > On 7/10/2012 6:04 PM, Timon Gehr wrote: >>> Functional means a guarantee against mutability. >> >> Only guaranteed referential transparency is necessary. Eg, 'fun' in the >> following code snippet has this property. Even though it mutates global >> state and calls a function that does not have the property. >> >> int x = 2; >> int fun(){ >> auto y = x; >> x = 3; >> auto r = gun(); >> x = y; >> return r; >> } >> >> auto gun(){ return arbitrary_pure_computation(x); } > > Purity also means not reading mutable global state. Also, it is not > practical for a compiler to ensure that x has the same value upon exit > as entry if it is being assigned to. > It could in principle ensure it by providing a language feature that carries out this operation. When the abstraction level gets higher, stuff usually gets much simpler for a verifier. > > >> I use unbounded data structures. Those have to be initialized lazily >> and therefore the non-constness of their methods invades the entire >> code base which is written in OO and functional styles (and a good >> portion of Metaprogramming to remove the boilerplate). Most methods >> that would be considered const cannot be, because they may trigger >> extensions of the unbounded data structures somewhere down the road. > > You do have another option - don't use toHash, opEquals, etc., with > those structures. > With the entire code base. The structures are fundamental. What can be const today will call into a method that uses one of those tomorrow. But why should I sprinkle my code with 'const' anyway? Almost none of its classes will actually be instantiated with the immutable qualifier. > >>> At least with @trusted the code inspector >>> knows that there's something unusual going on there that needs to be >>> manually checked. >> >> There will be non-trivial bugs that the code inspector won't see. >> Most bugs are caught by testing or trying to formally prove correctness. > > I cannot reconcile that with a desire for logical constness, which is > completely uncheckable. > 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. > Anyhow, the point of @trusted is to notify the maintainer that "here be > dragons". Logical const doesn't even go that far - it's purely ornamental. I see. I was under the impression that the point of @trusted was to be able to subvert the type system without actually subverting its guarantees. |
July 11, 2012 Re: Inherited const when you need to mutate | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 7/10/12 7:57 PM, Walter Bright wrote: > On 7/10/2012 4:16 PM, Timon Gehr wrote: >> Const is stronger than what is required to bridge the gap between >> mutable and immutable. It guarantees that a reference cannot be used to >> mutate the receiver regardless of whether or not the receiver is >> immutable underneath. That is unnecessary as far as immutable is >> concerned. It only needs to guarantee that the receiver does not change >> if it is immutable underneath. > > If you have a const function that accepts both mutable and immutable > args, then that function *by definition* cannot tell if it received > mutable or immutable args. It can if immutable(T) is invalid. > Furthermore, a const function is saying it will not change, even if > mutable data is passed to it. Lazy computation does not produce detectable change. > Everything falls apart once you allow "logical const" in. You'll be in > the same boat as C++ const, which is faith-based programming rather than > checkable programming. Then please do not suggest pimpl and casting. That is UNDEFINED. If you want to ascribe any meaning to it, you ruin all the work we've done on immutability. Andrei |
July 11, 2012 Re: Inherited const when you need to mutate | ||||
---|---|---|---|---|
| ||||
Posted in reply to Andrei Alexandrescu | On 07/11/2012 03:43 AM, Andrei Alexandrescu wrote:
> On 7/10/12 6:39 PM, Walter Bright wrote:
>> On 7/10/2012 3:13 PM, H. S. Teoh wrote:
>>> I don't think they were rushed. There's been a push for making druntime
>>> and Phobos const-correct for a while now. I don't think this change is a
>>> _mistake_ per se, but it does expose a flaw in the language: const is
>>> too limited in scope, and we need something else to fill in for use
>>> cases where const isn't good enough.
>>
>>
>> There's a gigantic problem with logical const. It is completely
>> unenforceable - it is documentation only. All the reasoning and
>> mechanical guarantees that come with const, immutable, and purity fall
>> apart.
>
> I think we need to unlock that mindset lest we risk short-circuiting all
> reason by always (constantly, heh) falling back on dogma a la "logical
> const is unenforceable". This is a very damaging pattern.
>
> We can devise a number of solutions in which there's lazy computation
> inside const methods, while still preserving the current guarantees. The
> only issue with those is they complicate the definition and
> implementation of D.
>
> So the question remains whether the complication is worth it. I
> personally am not convinced. Sure, examples can be found if one looks
> real hard, but I think their importance is overstated when the community
> gets in the mood of proving their need. Fact is, the use of "mutable" in
> C++ is very scant even in codebases that use const religiously (like
> ours). A cursory search yielded 0.01% as many uses of "mutable" as
> "const" in our very large codebase.
>
>
> Andrei
This is unfortunately easily explained using the fact that const is not transitive in C++.
|
July 11, 2012 Re: Inherited const when you need to mutate | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 7/10/12 7:58 PM, Walter Bright wrote:
> If you've found a way to mutate const and have it stay const, then
> there's a hole in the typing system.
No. This is dogma devoid of substance. Maybe you found a way to initialize on first read, and suddenly there's a viable type system.
Andrei
|
July 11, 2012 Re: Congratulations to the D Team! | ||||
---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 7/10/12 8:03 PM, Walter Bright wrote:
> On 7/10/2012 4:29 PM, Timon Gehr wrote:
>> On 07/11/2012 01:10 AM, Walter Bright wrote:
>>> On 7/10/2012 3:49 PM, Timon Gehr wrote:
>>>>> I understand, but the downside of not making these functions const
>>>>> is it
>>>>> will torpedo the use of functional style programming in D.
>>>>>
>>>>
>>>> Making these functions const will torpedo the use of OO style
>>>> programming in D.
>>>
>>> No, it won't, there are simple workarounds.
>>>
>>
>> In @safe code there are none.
>
> Right. That's what I meant when you have to add @trusted.
That's @undefined, not @trusted.
Andrei
|
Copyright © 1999-2021 by the D Language Foundation