May 29, 2013
On 29/05/13 11:31, Steven Schveighoffer wrote:
>
>> I'm implementing sets and the concept is sets of objects not sets of
>> the values in the objects.  I want to be able to initialize sets using
>> arrays and I'm promising that I won't change the array or its
>> contents.   I'm also trying to promise that I won't (inside the set
>> implementation) make any changes to individual objects.  But I'm not
>> promising that they won't be changed by other code that has access to
>> them.  The invariant (I wrote) for the sets implies that any changes
>> made by that code can't change the sort order of the objects.
>
> Then you are trying to make a *momentary* guarantee.

Almost.  I'm also saying I won't change it any time within the set.

>
> In essence, you want to transform the array into a set, but still
> guarantee that the underlying data hasn't changed.

More or less. The set code won't change it something else might.

>
> This is very difficult to express with the type system.

I'm starting to realize this :-)

I've always been frustrated in other languages by const (and friends) being inadequate for saying what I want to say.

> The closest you
> can get is inout, which *might* be able to do it.
>
> For example:
>
> inout(T)[] copyIt(T)(inout(T)[] arr)
> {
>     // this is quirky, but it's the easiest way to do it without an
> equivalent .dup for inout
>     inout(T)[] result;
>     result ~= arr;
>     return result;
> }
>
> Now, if you pass in mutable, you get mutable.  If you pass in const, you
> get const.  If you pass in immutable, you get immutable.
>
> I say *might* because I don't know what your code is doing, or how your
> set type actually looks.  It may be logical, but impossible to currently
> express.  inout still has some room for improvement (and so does the
> whole const system in general).
>
>> No, I really don't want copies although I can see that might have it's
>> uses in another application.  It's the mathematical concept of sets
>> that I'm trying for not just containers.  I.e. it's a way of selecting
>> groups of objects, combining groups without duplicates, etc.
>
> Another option is to simply define your types as "immutable" types.
> That is, specify the data inside is immutable, but the class itself is not.

No.  I need to be able to make changes to the objects (that doesn't effect their order).  It's kind of like an associative array (which I did consider as a mechanism for doing what I wanted) but without splitting the object into a key/value pair.

One place I'm using it is for managing symbols (look ahead sets, etc) in a parser generator.  The symbols' permanent home is in an associative array indexed by their name (which is also included in the symbol object) but they may belong to many look ahead sets.  Usually, processing that requires access to attributes of the symbol (other than the key) occurs when processing one of the look ahead sets and going looking for the symbol table looking for the data is an unnecessary complication (not to mention an efficiency hit).  Having exactly one object per symbol but still being able to have it multiple sets is a big efficiency gain especially when it comes to making changes to the ancillary data as you only have to change it in one place.

>
> e.g.:
>
> class C
> {
>     immutable int c;
>     this(int x) { c = x;}
> }
>
> You are hitting a very common pain-point for const/immutable, for which
> there is really not a good answer currently.

I agree.

This happens every time I try a new language.  I get all excited about making my code bulletproof only to be mugged by reality. :-)  In the end, it comes down to coding carefully.

Peter

May 29, 2013
On Tue, 28 May 2013 23:43:49 -0400, Peter Williams <pwil3058@bigpond.net.au> wrote:

> No.  I need to be able to make changes to the objects (that doesn't effect their order).  It's kind of like an associative array (which I did consider as a mechanism for doing what I wanted) but without splitting the object into a key/value pair.
>
> One place I'm using it is for managing symbols (look ahead sets, etc) in a parser generator.  The symbols' permanent home is in an associative array indexed by their name (which is also included in the symbol object) but they may belong to many look ahead sets.  Usually, processing that requires access to attributes of the symbol (other than the key) occurs when processing one of the look ahead sets and going looking for the symbol table looking for the data is an unnecessary complication (not to mention an efficiency hit).  Having exactly one object per symbol but still being able to have it multiple sets is a big efficiency gain especially when it comes to making changes to the ancillary data as you only have to change it in one place.

OK, this gives more info.

You can't use const elements.  And for good reason -- the elements are not const, and cannot be considered const by the set.

Use mutable data, and make the *key* part of the data const.  If you wish, you can place inout on the indexing function, which should guarantee that during that function the data is not molested.  Then you basically have to hand-verify the insertion function does not change the data.

That's the best D can do, and it's pretty good considering what other alternatives there are.

-Steve
May 29, 2013
On 29/05/13 13:59, Steven Schveighoffer wrote:
>
> You can't use const elements.  And for good reason -- the elements are
> not const, and cannot be considered const by the set.
>
> Use mutable data, and make the *key* part of the data const.

My first implementation does that and it's great for that one application.  All the complications arose when I went back to try and bulletproof it and make a set implementation that is generally useful rather than just for the application it was designed for.

I may just have to accept that two separate implementations are needed and that I have to give some thought about how to maximise the shared code between them.

>  If you
> wish, you can place inout on the indexing function, which should
> guarantee that during that function the data is not molested.  Then you
> basically have to hand-verify the insertion function does not change the
> data.

I always (try to) do hand verification :-).  Once I even toyed with code proofs (a la Gries, ISBN 0-387-90641-X) but that rapidly becomes boring in practice even when you have language constructs (such as Eiffel's loop) which are designed with such proofs in mind.

>
> That's the best D can do, and it's pretty good considering what other
> alternatives there are.

Yes. I agree - a type attribute system that could say everything we want to say would be very complex.  Nevertheless, D has room for improvement e.g. more expressive contracts are possible.

Peter
PS There seems to be a reluctance to fix problems in D if it will break existing code.  I would opine that the code that would be broken is already broken.  I would also opine that fixing the problems now will break less code than fixing them later.
May 29, 2013
On 05/28/2013 07:24 PM, Jonathan M Davis wrote:

> On Tuesday, May 28, 2013 18:19:49 Ali Çehreli wrote:
>> I remember the discussions on that topic and various syntax proposals
>> but I don't remember why class variable assignment syntax would not work:
>
> The problem is that the type system does not differentiate between a class
> object and a reference to a class object. The type refers explicitly to the
> reference, not the object. It doesn't have the concept of a class object
> separate from its reference

However, most of the operations on a class reference are relayed to the object:

  auto r = new C();
  r.foo();            // relayed to the object
  r.bar = 42;         // relayed to the object
  writeln(r);         // relayed to the object
  // ...              // etc.

With exception of assignment, which stays with the reference:

  r = new C();        // on the reference

How about the following two rules, which do not require any syntax change. Let's make it so that there are two kinds of type checks on a class reference:

1) For operations that involve the object, perform the type check as it is today.

2) The assignment operation is type-checked specially:

  const: Allow the assignment for any type on the right-hand side)

  immutable: Allow the assignment for only immutable on the right-hand side

  mutable: Allow the assignment for only mutable on the right-hand side

Of course this throws out the current behavior of non-mutable references being non-rebindable, but I think non-rebindable references are overrated. Even in C and C++, most of the time it is the data that is const. For example, nobody takes 'const char * const' parameters. Yes, it is helpful in theory, but programmers simply define the parameter as 'const char *'. (Or 'char const *' if they are more purist. :) )

It seems simple enough but perhaps it is too difficult to implement at this point. Only Kenji can tell! ;)

Ali

May 29, 2013
On Tuesday, May 28, 2013 22:58:39 Ali Çehreli wrote:
> On 05/28/2013 07:24 PM, Jonathan M Davis wrote:
>  > On Tuesday, May 28, 2013 18:19:49 Ali Çehreli wrote:
>  >> I remember the discussions on that topic and various syntax proposals
>  >> but I don't remember why class variable assignment syntax would not
> 
> work:
>  > The problem is that the type system does not differentiate between a
> 
> class
> 
>  > object and a reference to a class object. The type refers explicitly
> 
> to the
> 
>  > reference, not the object. It doesn't have the concept of a class object
>  > separate from its reference
> 
> However, most of the operations on a class reference are relayed to the object:
> 
>    auto r = new C();
>    r.foo();            // relayed to the object
>    r.bar = 42;         // relayed to the object
>    writeln(r);         // relayed to the object
>    // ...              // etc.
> 
> With exception of assignment, which stays with the reference:
> 
>    r = new C();        // on the reference
> 
> How about the following two rules, which do not require any syntax change. Let's make it so that there are two kinds of type checks on a class reference:
> 
> 1) For operations that involve the object, perform the type check as it is today.
> 
> 2) The assignment operation is type-checked specially:
> 
>    const: Allow the assignment for any type on the right-hand side)
> 
>    immutable: Allow the assignment for only immutable on the right-hand side
> 
>    mutable: Allow the assignment for only mutable on the right-hand side
> 
> Of course this throws out the current behavior of non-mutable references being non-rebindable, but I think non-rebindable references are overrated. Even in C and C++, most of the time it is the data that is const. For example, nobody takes 'const char * const' parameters. Yes, it is helpful in theory, but programmers simply define the parameter as 'const char *'. (Or 'char const *' if they are more purist. :) )
> 
> It seems simple enough but perhaps it is too difficult to implement at this point. Only Kenji can tell! ;)

Having const references or pointers is occasionally useful, but we wouldn't lose much IMHO if we lost them. Java got it backwards in that that's the _only_ kind of const that it has (via final). However, I don't know how feasible your suggestion is. Remember that there isn't really any difference between the reference and the object in the type system. When you mark a function as const, it's the this reference which is const. So, when you have a const C that you're operating on, it's the reference itself which is then the this reference and is passed around as const. As far as the type system is concerned, there's nothing else to _be_ const. So, you're asking for it to be treated as const as far as function calls go but treated as mutable otherwise, which goes against how const works. You're asking for a half-constness of sorts.

To make matters worse, what happens when you have a const object which has a reference as a member variable? e.g.

class C
{
    class D d;
}

const C c;

Because of the transitivity of const, d must be fully const, and yet with your suggestion, it isn't. Or if it is, we now have a situation where the constness of a reference depends on its context, and the compiler must keep track of its context in addition to its type in order to determine whether it's fully const or just half const.

So, while at first glance, I think that your suggestion makes sense conceptually, I think that it's going to fall apart when we get into the details.

I confess that for the most part, I've just given up and resigned myself to using Rebindable.

- Jonathan M Davis
May 29, 2013
On Wednesday, 29 May 2013 at 01:05:14 UTC, Peter Williams wrote:
> I'm implementing sets and the concept is sets of objects not sets of the values in the objects.  I want to be able to initialize sets using arrays and I'm promising that I won't change the array or its contents.
>  I'm also trying to promise that I won't (inside the set implementation) make any changes to individual objects.  But I'm not promising that they won't be changed by other code that has access to them.  The invariant (I wrote) for the sets implies that any changes made by that code can't change the sort order of the objects.
>
> Peter

The problem is that you are missing a FUNDAMENTAL difference between C++ and D. C++'s const merely says: "Though shalt not modify this value", whereas D's means "This object's value WILL remain constant, until the end of time, and under no circumstance can it ever be modified. Oh. And so will everything it references".

The fact that *you* are promising to not change it is irrelevant to the concept of D's const. D only knows 2 states: objects that mutate, and objects that don't mutate.

Ditto about invariants. Contrary to C++, there is no concept of "observable constness" (eg invariants).

The reasoning behind this is that in D, const is actually the "base attribute" between the "muttable" and the "immutable" objects. immutable allows sharing things accross threads, and allows more aggressive optimizations than is possible with const "the compiler *knows* the object will not be changed by an outside force". At this point, const exists only to be able to operate on objects that may or may not be immutable.

For example, imagine you have a const array of pointers, and you

--------
Of course, this has its limits, and that's where casting comes into play. You must keep in mind these two caveats:
*Never ever ever modify something that is const.
*Once you've casted something to const, make sure that NOTHING will modify the object via a non-const handle.

But the "const" you are trying to promise is not what D's const was designed for.
May 29, 2013
On Wednesday, 29 May 2013 at 09:40:08 UTC, monarch_dodra wrote:
> On Wednesday, 29 May 2013 at 01:05:14 UTC, Peter Williams wrote:
>> I'm implementing sets and the concept is sets of objects not sets of the values in the objects.  I want to be able to initialize sets using arrays and I'm promising that I won't change the array or its contents.
>> I'm also trying to promise that I won't (inside the set implementation) make any changes to individual objects.  But I'm not promising that they won't be changed by other code that has access to them.  The invariant (I wrote) for the sets implies that any changes made by that code can't change the sort order of the objects.
>>
>> Peter
>
> The problem is that you are missing a FUNDAMENTAL difference between ...

Erm, I posted this without seeing there was more to the thread. So I could be wrong. But I think the point is still relevant.
May 29, 2013
On Wednesday, 29 May 2013 at 09:40:08 UTC, monarch_dodra wrote:
> whereas D's means "This object's value WILL remain constant, until the end of time, and under no circumstance can it ever be modified. Oh. And so will everything it references".

That's NOT what D's const means! The data may change through other, mutable references to it, but not through any const references. That's why const data is not implicitly convertible to immutable data (unless it has no mutable indirection, in which case it would be a deep copy) - there could be mutable references out there!

The difference between C++ and D const is that D's const is transitive, and mutating it by casting away const can have terrible consequences because const is designed to work with immutable.

> The fact that *you* are promising to not change it is irrelevant to the concept of D's const. D only knows 2 states: objects that mutate, and objects that don't mutate.

No.

> *Once you've casted something to const, make sure that NOTHING will modify the object via a non-const handle.

This is *perfectly fine* unless the non-const handle was acquired by casting away const or immutable.
May 29, 2013
On Wednesday, 29 May 2013 at 12:23:04 UTC, Jakob Ovrum wrote:
> On Wednesday, 29 May 2013 at 09:40:08 UTC, monarch_dodra wrote:
>> whereas D's means "This object's value WILL remain constant, until the end of time, and under no circumstance can it ever be modified. Oh. And so will everything it references".
>
> That's NOT what D's const means! The data may change through other, mutable references to it, but not through any const references. That's why const data is not implicitly convertible to immutable data (unless it has no mutable indirection, in which case it would be a deep copy) - there could be mutable references out there!
>
> The difference between C++ and D const is that D's const is transitive, and mutating it by casting away const can have terrible consequences because const is designed to work with immutable.
>
>> The fact that *you* are promising to not change it is irrelevant to the concept of D's const. D only knows 2 states: objects that mutate, and objects that don't mutate.
>
> No.
>
>> *Once you've casted something to const, make sure that NOTHING will modify the object via a non-const handle.
>
> This is *perfectly fine* unless the non-const handle was acquired by casting away const or immutable.

Hum.

...

Sight. I think I let immutable get to my head again :/

You are right. My point was moot.
May 29, 2013
"Michel Fortin" <michel.fortin@michelf.ca> wrote in message news:ko3ri4$kkb$1@digitalmars.com...
>>
>> Michel Fortin has created a pull request to make
>>
>> const(T)ref
>>
>> work, and only apply the const to the data.  It's certainly doable.  But the syntax, as you can see, is ugly.
>
> Well, that pull request wasn't trivial to implement correctly and the syntax took some time to come with. And also there's no guaranty Walter would have accepted the somewhat contorted solution even though it was working pretty well (but with no review comment it's hard to say).
>

Manu and I had a conversation about this on the last night of dconf, a came up with a slight improvement -

Introduce *C (syntax not important) to give you the raw class type, much like the raw function type.  You can then apply const directly to this type, and an appropriate suffix gets you back to the reference.  (eg shared(const(*C) ref) )  No more optional suffix, no more need to collapse chains of 'ref' back into a single type.

This should reduce the compiler changes required, as I recall much of the complexity was due to changing the meaning of the existing type.

This would also play better with template argument deduction, as there was no clear way to define it when ref was optional.  The inconsistent handling of arrays and pointers has since been fixed (eg const(T*) matching const(U*), U becomes const(T)* and the same for arrays) so there is a clear pattern to follow.

In terms of syntax, I'm leaning towards asterix for prefix and postfix, ie
shared(const(*C)*)