July 10, 2012
On 7/9/2012 3:27 PM, Jakob Ovrum wrote:
> This opEquals is only logically constant, not bitwise constant, hence it must
> not be const; similar to the caching scenario. Just trying to demonstrate that
> the issue is bigger than just caching - it's any logically constant comparison
> function.

I understand, but the downside of not making these functions const is it will torpedo the use of functional style programming in D.

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.
July 10, 2012
On Tue, Jul 10, 2012 at 11:59:48PM +0200, Jakob Ovrum wrote:
> On Tuesday, 10 July 2012 at 21:18:18 UTC, H. S. Teoh wrote:
> >If I'm OK with physical const, then all is fine and dandy.  But as soon as one thing can't be const, I've to re-engineer my entire framework from ground up.
> >
> >Isn't there something we can do to improve this situation?
> >
> >
> >T
> 
> I don't think the answer is to change D's const. D's const, unlike C++'s const, only exists to bridge immutable and mutable data. As soon as it becomes incompatible with immutable (which C++'s const very much is), it ceases to be purposeful.

I don't think we want to change physical (bitwise) const. It does have its uses, and it's necessary if you want immutable to work nicely with mutable. But if the current const is all we have, then IMO something is missing from the language.


> The problem arises when const is forced upon interfaces like toString and opEquals. we don't expect these functions to change observable state. In other words, we expect them to be logically constant, but not necessarily bitwise constant. That's not something the compiler can enforce, and it shouldn't try to. Other operators, e.g. opIndex, correctly leave the responsibility to the programmer.

Exactly, that's what I mean, we're using bitwise const in druntime in places where we actually intended _logical_ const. Bitwise const is a subset of logical const, and right now the subset is the only thing we have.


> There's also the reality that you want toString, opEquals, etc. to
> work with immutable (run-time type) class instances, requiring
> compatibility with const (compile-time type) references.
> 
> What I don't understand is why we've chosen const over mutable, when we should strive for allowing either and maybe even both. We've moved from one end of the scale to the opposite - our current situation is equally limiting to the previous situation.

I suppose the thought was that since const subsumes both mutable and immutable, it would be the ideal middle ground.  But it turns out not to be the case. (Or rather, it _is_ the case, just not quite what we'd imagined.)


> I think these changes were rushed; understandable considering the amount of pressure, but it just fixes one problem and creates an equally big new problem.

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.


T

-- 
Why have vacation when you can work?? -- EC
July 10, 2012
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.

July 10, 2012
On 07/11/2012 12:05 AM, Walter Bright wrote:
> On 7/9/2012 3:27 PM, Jakob Ovrum wrote:
>> This opEquals is only logically constant, not bitwise constant, hence
>> it must
>> not be const; similar to the caching scenario. Just trying to
>> demonstrate that
>> the issue is bigger than just caching - it's any logically constant
>> comparison
>> function.
>
> 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. OO is all about data encapsulation, aliasing and
mutation.

Also consider that:

- Functional style programs use structs and delegates, not class
  objects. Classes are an OO abstraction.

- The most well-known functional programming language, Haskell, mutates
  existing state a great deal at runtime, since almost _everything_ is
  lazily initialized.  I have written some Haskell-style D code, and
  could not use the 'const', 'immutable' or 'pure' qualifiers.

- what actually harms functional style programming in D the most is the
  lack of tail calls. (I have some other gripes, but this is the most
  important one.)

- D is multi-paradigm. A program can be functional style in many ways,
  and still mutate state. (Functional programming languages have to
  invent idioms and syntactic sugar to provide an abstraction for
  mutation. The compiler has to detect those and optimize them into
  mutation.)

> 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.

I didn't get that.
July 10, 2012
On Tue, Jul 10, 2012 at 03:39:30PM -0700, 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.
[...]

What about the "partial const" idea I had?

Given a class C which derives from a base class B, we may designate the base class B as const, which makes every inherited field const, while C itself may have mutable members. Then we can pass instances of C around as B references (it's OK to implicitly convert C to const(B), because the B part of C is actually const). As far as the user of the B references can tell, the object is const, even though the methods of C may be mutating C-specific members.

So C could be a caching class, or whatever it is that needs mutation to work, and B is its "const part" which is guaranteed by the type system never to mutate.


T

-- 
Give me some fresh salted fish, please.
July 10, 2012
On Tuesday, 10 July 2012 at 22:12:35 UTC, H. S. Teoh wrote:

> I don't think we want to change physical (bitwise) const. It does have
> its uses, and it's necessary if you want immutable to work nicely with
> mutable. But if the current const is all we have, then IMO something is
> missing from the language.

The purpose of the recent change is to allow operations like opEquals and toString on immutable class instances. If there is no immutable in the loop, you're not gaining anything by using const.

Even if we had a qualifier for something like logical const - let's call it lconst - it wouldn't be compatible with the current immutable and thus not the current const. It would be a separate system. lconst references would be implicitly convertible to const references, but not the other way around, equal to the current issue of not having both mutable and const options for opEquals and friends. You'd have exactly the same problem.

> Exactly, that's what I mean, we're using bitwise const in druntime in
> places where we actually intended _logical_ const. Bitwise const is a
> subset of logical const, and right now the subset is the only thing we
> have.

These changes are *not* to help programmers make methods like opEquals and toString follow previously implicit rules, they're here to make these methods work with const (yes, the bitwise const) and thus immutable.


> I suppose the thought was that since const subsumes both mutable and
> immutable, it would be the ideal middle ground.  But it turns out not to
> be the case. (Or rather, it _is_ the case, just not quite what we'd
> imagined.)

We knew exactly what this meant for opEquals implementations that weren't bitwise const but otherwise completely reasonable. I remember posting about these issues months ago, and I've also posted in the relevant Github pull request.

> I don't think they were rushed. There's been a push for making druntime
> and Phobos const-correct for a while now.

Yes, the discussion has existed ever since const was introduced. I said I think the proposed solution was rushed, as people had pointed out several issues with it that nobody were able to address.

> 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.
>
>
> T

I think this is a red herring. The ultimate goal is to make these methods work with immutable class instances, which means the current const is exactly what we want to support.

We just have to make sure to do it in a way that doesn't compromise on methods requiring mutability. I wish I had a good solution for that, but the fact that I don't have one doesn't make the problem any less real.
July 10, 2012
On 7/10/2012 4:05 PM, H. S. Teoh wrote:
> On Tue, Jul 10, 2012 at 03:39:30PM -0700, 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.
> [...]
>
> What about the "partial const" idea I had?
>
> Given a class C which derives from a base class B, we may designate the
> base class B as const, which makes every inherited field const, while C
> itself may have mutable members. Then we can pass instances of C around
> as B references (it's OK to implicitly convert C to const(B), because
> the B part of C is actually const). As far as the user of the B
> references can tell, the object is const, even though the methods of C
> may be mutating C-specific members.
>
> So C could be a caching class, or whatever it is that needs mutation to
> work, and B is its "const part" which is guaranteed by the type system
> never to mutate.

Logical const is still mutating const members.


July 10, 2012
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.


> OO is all about data encapsulation, aliasing and
> mutation.
>
> Also consider that:
>
> - Functional style programs use structs and delegates, not class
>    objects. Classes are an OO abstraction.
>
> - The most well-known functional programming language, Haskell, mutates
>    existing state a great deal at runtime, since almost _everything_ is
>    lazily initialized.  I have written some Haskell-style D code, and
>    could not use the 'const', 'immutable' or 'pure' qualifiers.
>
> - what actually harms functional style programming in D the most is the
>    lack of tail calls. (I have some other gripes, but this is the most
>    important one.)
>
> - D is multi-paradigm. A program can be functional style in many ways,
>    and still mutate state. (Functional programming languages have to
>    invent idioms and syntactic sugar to provide an abstraction for
>    mutation. The compiler has to detect those and optimize them into
>    mutation.)
>
>> 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.
>
> I didn't get that.

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.
July 10, 2012
On 07/11/2012 12:39 AM, 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.
>

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.
July 10, 2012
On Tue, Jul 10, 2012 at 04:05:51PM -0700, Walter Bright wrote:
> On 7/10/2012 4:05 PM, H. S. Teoh wrote:
[...]
> >What about the "partial const" idea I had?
> >
> >Given a class C which derives from a base class B, we may designate the base class B as const, which makes every inherited field const, while C itself may have mutable members. Then we can pass instances of C around as B references (it's OK to implicitly convert C to const(B), because the B part of C is actually const). As far as the user of the B references can tell, the object is const, even though the methods of C may be mutating C-specific members.
> >
> >So C could be a caching class, or whatever it is that needs mutation to work, and B is its "const part" which is guaranteed by the type system never to mutate.
> 
> Logical const is still mutating const members.
[...]

Not in this case. The const(B) reference does not permit any of B's methods to mutate the members of B -- you cannot downcast a const(B) reference to a C reference. As far as the methods of B are concerned, the object is immutable.

The interesting part is when C's methods override B's methods: those methods _can_ mutate the object, but not the members inherited from B. And this does not break immutability; you cannot cast const(B) to C, so if you start with immutable(B), it will remain immutable no matter what. You can't call C's methods from the const(B) reference, and you can't override B's methods with C's mutating methods without actually having a mutable C to begin with.


T

-- 
MAS = Mana Ada Sistem?