Index » General » Const sucks (page 8)

September 11, 2007
Janice Caron wrote:
> On 9/11/07, *Janice Caron* <caron800@googlemail.com <mailto:caron800@googlemail.com>> wrote:
> 
>     It's simple, but - I would argue - the wrong choice. A better choice
>     (in my humble opinion) would be
> 
>     const = constant
>     readonly = read-only
> 
>     If it's not too late to change the keywords, that, to me, would be
>     the way to go.
> 
> 
> 
> By which I mean, if it's at all feasable to consider ditching the keyword "invariant" altogether, and introducing a new keyword "readonly", then let's do it. "readonly" should indicate a read-only view of data which someone else might modify, and "const" should imply that no-one can modify it ever, no way, nohow.
> 
> I'm not quite sure why no one before now has suggested using "readonly" to mean read-only and "const" to mean constant, but seems kind of a no-brainer to me. You know - calling a thing what it is, instead of something it's not. I know I'd be dead confused if int meant float, for example.

It has been suggested before several times.  The problem is there's disagreement over what it should mean.  To some it is obvious that "readonly" should mean permanently unwriteable, just like "read only memory" is unwriteable.  To others it is equally obvious that it should mean a read-only view of data that is writeable through some other means.

--bb
September 11, 2007
On Tue, 11 Sep 2007 04:50:32 +0400, Miles <_______@_______.____> wrote:

> Walter Bright wrote:
>> Const, final, invariant, head const, tail const, it's grown into a
>> monster. It tries to cover all the bases, but in doing so is simply not
>> understandable.
>
> I was going to say that, that D's const sucks, many months ago, but
> didn't because I was afraid being regarded as a troll.
>
> Its not that I like const-less languages like Java (in fact, I hate
> them), but I think that C++'s const, with all its problems, was the
> right answer (for that time). It fixes the problem without putting much
> burden onto the programmer.
>
> D's const puts way too much burden onto the programmer, and gives very
> little back. Too many different combinations of const, final, invariant,
> pointers and references (and the fact that in D you don't immediately
> know if a type "T" is a reference of a value, specially while writing
> templates, adds to this confusion) with confusing semantics when
> combined (with a few special cases and lack of orthogonality).
>
> When the new const was being designed, I told in this list: "Don't try
> to fix what ain't broken". What I meant was: Don't try to do a new const
> just because you want a better const than C++s. Many people dislike C++s
> const, but are unable to point exactly why it is wrong. Most people
> doesn't even understand C++'s const, and try to get more from it than it
> was actually intended to provide (that is the whole point behind the new
> D's invariant).
>
> Sometimes people give use-cases to exemplify (STL's need for a
> difference between iterator<> and const_iterator<> is the first thing
> that comes to my mind), but then you ask what a language could provide
> to fix this, they don't know. This is where you should aim first you laser.
>
> When I first saw D, I was amazed with the possibilities. Now I'm
> somewhat scared. Perhaps one of the things that are scaring me is just
> like you said: there is a (at least one) monster in the language.

I'm totaly agree with all you have said.

-- 
Regards,
Yauheni Akhotnikau
September 11, 2007

Walter Bright wrote:
> Const, final, invariant, head const, tail const, it's grown into a monster. It tries to cover all the bases, but in doing so is simply not understandable.

Does that mean I'm a genius, or insane? :P

> [snip]
> 
> o  no more final

Not 100% sure what you mean by "no more".  But then, "final" was always kind of like the appendix of the new const system; I always viewed it as something of a side issue since it didn't affect the type of declarations.

> o  const and invariant now mean "fully const" and "fully invariant", where fully means "head and tail combined":
> 
> const int x = 0;   // x is constant
> const int* p = &x;  // neither p nor *p can be changed
> const(int*) p = &x;  // neither p nor *p can be changed
> const(int)* p = &x;  // p can change, *p cannot be changed

Ok.

> o  const and invariant are transitive. There is no way to specify a
> (pointer to)(const pointer to)(mutable int).
> 
> o  tail invariant for an array or pointer type can be done using the existing syntax:
> 
>  invariant(char)[] s;   // string, i.e. an array of invariant chars
>  const(T)* p;  // pointer to tail const T

This makes me happy, because although I understood the what, I never could grasp the why of "invariant(char)[]" meaning that you couldn't change the elements of the array.

> o  tail const of a struct would have to be done by making the struct a template:
> 
>   struct S(T) { T member; }
>   S!(int)   // tail mutable
>   S!(const(int)) // tail const

This is... not so good.  I'll come back to this...

> o  one can construct a template to generically produce tail const or tail invariant versions of a type.

This makes me rather nervous; it seems like a bad thing that we can no longer do tail-const, and have to resort to wrapping templates to do it.

I mean, in the end, you can make a const version of any type by replacing all fields with read-only properties; but then you can't cleanly cast between them.

> o  it will be illegal to attempt to change the key value of a foreach loop, but the compiler will not be able to diagnose all attempts to do so

I assume this means that for built-in types, instead of being

  ( ref key, ref value ; aggregate )

We'll have

  ( const ref key, ref value ; aggregate )

If so; I'll be happy about that.  So long as I can do evil cr*p like:

  ( ref idx, ref chr ; string.foo ) ++idx;

Why, you ask?  Er, you don't wanna know... :P

> o  static const/invariant means the initializer is evaluated at compile time. non-static const/invariant means it is evaluated at run time.

Ok.

> o  no initializer means it is assigned in the corresponding constructor.

Sounds good.

> o  const/invariant declarations will always allocate memory (so their
> addresses can be taken)

I guess this is good from a consistency standpoint.

> o  So, we still need a method to declare a constant that will not consume memory. We'll co-opt the future macro syntax for that:
> 
>     macro x = 3;
>     macro s = "hello";

The syntax doesn't *really* speak to me; honestly, I was expecting you
to use "alias" before I scrolled down and saw "macro".  Thing is, if
this works for arbitrary expressions, does that mean this will be possible:

    macro string = char[];

My main problem with this is the loss of tail const on structs/classes.
 Others have already listed several useful things that you won't be able
to do because of this, so I won't bother with that.

This problem seems to be unique to structs and classes.  This is because every other reference type is written in such a way that you can "split" the type in such a way as to get tail-const semantics.  For example: const(char[]) versus const(char)[].  This can't be done with structs or classes since their types are single identifiers.

So what we need is some way to write that split.  Really, what we're saying is "I don't want *this* to be const, but I do want what it references to be const".

Back when I was writing my article on the upcoming const changes, I gave each kind of const a different, longer name to help differentiate them; the names I gave const and invariant were "reference const" and "reference immutable".

So, stealing a little bit of C++ syntax, what about "const& T" being a tail-const version of some struct or class T?  Since this could potentially introduce synonyms in other places, I would also propose the following:

  const& int a; // Error: cannot use reference const on an atomic type
  const& int* b; // Error: equivalent to const(int)*
  struct V { int a; }
  const& V c; // Warning: V does not have any references
  struct R { int* a; }
  const& R d; // OK
  class C {}
  const& C e; // OK

Yes, this means there is a distinction between structs, classes and "everything else".  But honestly, I don't think we can have tail const without this distinction.  Maybe disallowing const& on arrays and pointers isn't a good idea; but I do think we need some kind of tail-const for structs and classes.

Thoughts?

	-- Daniel
September 11, 2007

Bill Baxter wrote:
> Janice Caron wrote:
>> On 9/11/07, *Janice Caron* <caron800@googlemail.com <mailto:caron800@googlemail.com>> wrote:
>>
>>     It's simple, but - I would argue - the wrong choice. A better choice
>>     (in my humble opinion) would be
>>
>>     const = constant
>>     readonly = read-only
>>
>>     If it's not too late to change the keywords, that, to me, would be
>>     the way to go.
>>
>>
>>
>> By which I mean, if it's at all feasable to consider ditching the keyword "invariant" altogether, and introducing a new keyword "readonly", then let's do it. "readonly" should indicate a read-only view of data which someone else might modify, and "const" should imply that no-one can modify it ever, no way, nohow.
>>
>> I'm not quite sure why no one before now has suggested using "readonly" to mean read-only and "const" to mean constant, but seems kind of a no-brainer to me. You know - calling a thing what it is, instead of something it's not. I know I'd be dead confused if int meant float, for example.
> 
> It has been suggested before several times.  The problem is there's disagreement over what it should mean.  To some it is obvious that "readonly" should mean permanently unwriteable, just like "read only memory" is unwriteable.  To others it is equally obvious that it should mean a read-only view of data that is writeable through some other means.
> 
> --bb

The problem is that, broadly speaking, all these words mean the same thing.  Once you get down to splitting hairs over exactly how constant/invariant/immutable/readonly/final something is, you're always going to find some (possibly obscure) argument against the way you want it to work.

Aren't natural languages fun?!

:P

	-- Daniel
September 11, 2007
On 9/11/07, Derek Parnell <derek@nomail.afraid.org> wrote:
>
> In other words, if I have a struct with three members, each of a different type, I need to code ...
>
> struct S3(T, U, V)
> {
>    T member1;
>    U member2;
>    V member3;
> }
>
> S3!(const(int), const(float), const(bool));
>
> and so on for 4, 5, 6, .... 23 member structs.
>
> I'm sure I'm misunderstanding you, because this is really silly.
>

I don't think you're misunderstanding. I think that's what Walter is saying.

But here's another idea. If it were allowable that
(1) an alias template parameter could accept a type constructor, and
(2) "auto" were accepted as a do-nothing type constructor
then you would be able to do this:

 struct S(alias X)
 {
     X(int)* pi;
     X(float)* pf;
     X(double)* pd;
 };

 S(const) k; // k's members are tail-const
 S(auto) m; // m's members are mutable


September 11, 2007
Daniel Keep wrote:
 > This problem seems to be unique to structs and classes.  This is because
> every other reference type is written in such a way that you can "split"
> the type in such a way as to get tail-const semantics.  For example:
> const(char[]) versus const(char)[].  This can't be done with structs or
> classes since their types are single identifiers.

I came to the same conclusion, the basic problem is the inability to put a * or [] outside the () of const.

> So what we need is some way to write that split.  Really, what we're
> saying is "I don't want *this* to be const, but I do want what it
> references to be const".

Exactly.

> So, stealing a little bit of C++ syntax, what about "const& T" being a
> tail-const version of some struct or class T?  Since this could
> potentially introduce synonyms in other places, I would also propose the
> following:
> 
>   const& int a; // Error: cannot use reference const on an atomic type
>   const& int* b; // Error: equivalent to const(int)*
>   struct V { int a; }
>   const& V c; // Warning: V does not have any references
>   struct R { int* a; }
>   const& R d; // OK
>   class C {}
>   const& C e; // OK
> 
> Yes, this means there is a distinction between structs, classes and
> "everything else".  But honestly, I don't think we can have tail const
> without this distinction.  Maybe disallowing const& on arrays and
> pointers isn't a good idea; but I do think we need some kind of
> tail-const for structs and classes.
> 
> Thoughts?

In D when we write:

Foo f;

we are declaring a reference to a Foo.  The problem, as you mentioned is the inability to seperate the reference from the Foo for purposes of declaring const, so...

I was initially thinking that for classes we might write:

class Foo { int a; }
const(Foo)& pFoo;  //pFoo can change, pFoo.a cannot

Where the & doesn't introduce another reference or level of indirection, in fact it does _nothing_ at all except allowing us to place it outside the () of const.

Meaning, that these two declarations would in fact be identical:

Foo& pFoo;
Foo  pFoo;

which will no doubt bother some people, perhaps a lot of people?

The first form could be made illegal, after all it's pointless(right?) to take a reference to a reference.

Alternately, and I think I might prefer this solution, perhaps _adding_ something to the const() is the solution. eg.

class Foo { int a; }
const(*Foo) pFoo;  //pFoo can change, pFoo.a cannot

As in, were saying "the value of Foo is const", therefore implying the reference is not.


As for structs, as someone else has mentioned there isn't really any difference between making a struct variable const and making all members of that struct const, without a pointer or reference they are the same thing.  So, saying:

struct Bar { int a; Foo pFoo; }
const(Bar) bar;

Is perfectly sufficient in that case.

The difference occurs when you want to make _some_ of the members consts and not the others, typically you want to make references/pointers const, or tail const.

The syntax:

struct Bar { int a; Foo pFoo; }
const(Bar)& bar;

doesn't make the same sense for structs because there is no reference involved (as many programmers would expect upon reading that).

Neither does the * syntax I proposed above:

struct Bar { int a; Foo pFoo; }
const(*Bar) bar;

because that would logically make all members of the struct const, and we get that already.

Assuming there is even a requirement to make select members of a struct (initially declared without const) const (if there isn't we have no problem) then using a template seems the most sensible solution.  It would also allow you to apply const or tail const where appropriate. eg.

From this initial struct:

struct Bar {
  int a;     //we want this to be const
  Foo pFoo;  //we want this to be tail const
}

Our template generates this:

struct BarConst
{
  const int a;
  const(*Foo) pFoo;
}

Regan
September 11, 2007
Bruno Medeiros wrote:
> Derek Parnell wrote:
>>  
>>> o  const and invariant now mean "fully const" and "fully invariant", where fully means "head and tail combined":
>>
>> Remind me again what the difference between 'const' and 'invariant' is.
>>
> 
> 'invariant' = constant
> 'const' = read-only
> 
> It's simple I think.
> 

I must withdraw somewhat my comment that "it's simple", since "read-only" is indeed a somewhat subjective term. If you think read-only as in permissions, you get the intended meaning. But if you think read-only as in memory (ROM), as Bill mentioned, you get the same meaning as constant. Still, I think the "read-only as in permissions" meaning for "read-only" is becoming more prevalent as it is more... meaningful.

-- 
Bruno Medeiros - MSc in CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
September 11, 2007
Regan Heath wrote:
> Assuming there is even a requirement to make select members of a struct (initially declared without const) const (if there isn't we have no problem) then using a template seems the most sensible solution.  It would also allow you to apply const or tail const where appropriate. eg.

Talking to myself, a sure sign of mental instability ;)

The same applies to making select members of a class const.  I think it's important to remember that we're talking about _modifying_ an existing class/struct definition _adding_ const where it was not initially.

This would only be required in cases where:
1. you need different const rules for instances of the same class/struct in the same version of the code.
2. you need to const correct a 3rd party API.

In any other case you can simply apply const to the class/struct definition directly, no need for a template to do it. eg.

struct Something
{
  const(*Foo) pFoo;
  const int a;
  int b;
}

There will be no need to _modify_ this with a template because it will always need to have a mutable reference to a const Foo and that requirement won't be different for some instances and not others.  It may change in future versions of the code, but in that case a simple change to the struct definition is all you need, not a template.

I think the ability to tail const a class reference with something like:

class Foo { int a; }
const(*Foo) pFoo;

Handles 99% of the cases which we should realistically be worried about.

Regan
September 11, 2007
Walter Bright wrote:
> Bruno Medeiros wrote:
>> Walter Bright wrote:
>>> What, exactly, is the use case that needs a solution?
>>
>> Here's another use case, which is significantly different than the other (there should be lots more to be found). Say you have a Person class, which has a name, and for whatever reasons you want the name be a String class instead of const(char)[]. Then you have:
>>
>>   class Person {
>>     const(String) name;
>>     ...
>>
>> but now how do you change the name of the Person?...
> 
> I'd redesign String so it holds invariant data, then just have:
> 
> class Person {
>     String name;
>     ...
> 

Err... you do realize what this implies? This example holds not just for String but for the general case where one would want to use aggregation of immutable objects. In all those cases you'd have to redesign the aggregated class to be immutable, with all the annoyances that has. That basicly amounts to not be able to use const/invariant for classes, and then in that regard D would revert to the same status quo as Java and other languages.

(Another alternative is to use an actual pointer to a class reference, but that's sure to bring another slew of inconveniences)

-- 
Bruno Medeiros - MSc in CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
September 11, 2007
Janice Caron wrote:
> 
> I'm not quite sure why no one before now has suggested using "readonly" to mean read-only and "const" to mean constant, but seems kind of a no-brainer to me. You know - calling a thing what it is, instead of something it's not. I know I'd be dead confused if int meant float, for example.
> 

It was quite naive to think that no one had suggested keyword changes for this before. :P

-- 
Bruno Medeiros - MSc in CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
1 2 3 4 5 6 7 8 9 10 11 12
Top | Discussion index | About this forum | D home