www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Oskar's const proposal

reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Oskar Linde wrote:
 Walter Bright wrote:
 Janice Caron wrote:
 On 12/28/07, Walter Bright <newshound1 digitalmars.com> wrote:
 Janice Caron wrote:
 y is a /copy/ of x, and clearly it should be possible to make a copy
 of a const thing and have the copy be mutable.




*BEEP* Confusion warning: you mean *constant*, not "const". Huge difference. :)
 That doesn't work for structs or classes.

It doesn't? For structs struct S {} const S x; auto y = x;

Imagine you have: struct S { int* p; } Because const is transitive, const(S) implies that now p points to const. But if you strip off the const in the assignment, you've lost the const-ness of p, and now you have a gaping hole in the const-correctness.

The problem with the current const in D is the lack of orthogonality. As far as I can see, those problems would be solved by my orthogonal const proposal posted earlier. Constant values (such as structs) are by their nature always implicitly convertible to mutable values (worst case: just make a copy). The problem with the current const iteration is that there is no way to separate the orthogonal concepts constness (as in constants) from read-only access of references. Also, as far as I can see (not very) there would also not be any need for a third type of constant (the manifest one). const int x = 5; could behave like a template and only be instantiated when the address of x is taken. The result would be a const design, equally powerful but without spoiling the beauty and simplicity of the D1.0 const. In D1.0, when something is *constant*, you simply declare it as const: struct T { const int a = 5; int b; } static assert(T.sizeof == int.sizeof); No need to stop and think. In D2.0 it seems like you have to go through: "Hmm, this value will never change. I will mark it as const.... no wait invariant... or maybe manifest constant using the enumeration hack?"

(Referring to http://www.csc.kth.se/~ol/const.pdf in comments below that mention "your proposal") I think everyone has trouble seeing how your const proposal fixes the tricky issues. You give some syntaxes but don't show how they solve the problems that have caused all the consternation (!) over the past year that arise when you start pulling on the string. Also your comparison table doesn't include structs, which seems a big omission since structs often end up causing the trouble with const proposals. Also it would be helpful if your table gave an brief description of the behavior of each row: like "in int*: the pointer can be modified but the int cannot via this pointer" But anyway, if I understand from the brief description, the idea is basically to use "const" to apply to values only and to mean the value will not change, period, no way, never. And you use 'in' exclusively for things that can refer to other memory to say "you can't change what it refers to through this reference". Ok, that sounds pretty neat. So first question is: what happens when you use "in" on a plain int? It isn't a reference of any kind so is "in int" an error or a no-op? Does it have to be "const const(int)*"? Seems like that should be the same as "const(int*)". It's unclear in your proposal how const and in are scoped. I see some potential messiness given a pointer variable t of type 'T' determining whether I can modify what it points to or not. As far as I can tell, T can come in 3 non-modifiable flavors: * "in S*" (readonly ref to mutable S), * "in const(int)*" (readonly ref to immutable S), * and "const(int)*" (mutable ref to immutable S) So i'd need to check if either "typeof(*t) is const" or "typeof(t) is in". It also seems syntactically rather complex. Every reference type have both "in"-ness and "const"-ness, right? I take it both things are transitive?, so I can't have a const pointer to mutable pointer to const value. But even then If I have int****** how do I say it's 'in' from two levels down and 'const' from level 4 on down? in(const(int**)**)**. I guess I don't really understand why you'd ever have "const" appear twice in a declaration as you do for a couple of cases in your table. Both 'const' and 'in' I can see, but not const twice. Or are you trying to have tail const? Hope my questions are clear enough to you and give you some insight into what parts of your proposal people find difficult to understand. --bb
Jan 04 2008
parent Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
Bill Baxter wrote:
 Oskar Linde wrote:
  > Walter Bright wrote:
  >> Janice Caron wrote:
  >>> On 12/28/07, Walter Bright <newshound1 digitalmars.com> wrote:
  >>>> Janice Caron wrote:
  >>>>> y is a /copy/ of x, and clearly it should be possible to make a copy
  >>>>> of a const thing and have the copy be mutable.
  >
  > *BEEP* Confusion warning: you mean *constant*, not "const". Huge
  > difference. :)
  >
  >>>> That doesn't work for structs or classes.
  >>>
  >>> It doesn't? For structs
  >>>
  >>>     struct S {}
  >>>     const S x;
  >>>     auto y = x;
  >>
  >> Imagine you have:
  >>     struct S { int* p; }
  >> Because const is transitive, const(S) implies that now p points to
  >> const. But if you strip off the const in the assignment, you've lost
  >> the const-ness of p, and now you have a gaping hole in the
  >> const-correctness.
  >
  > The problem with the current const in D is the lack of orthogonality. As
  > far as I can see, those problems would be solved by my orthogonal const
  > proposal posted earlier. Constant values (such as structs) are by their
  > nature always implicitly convertible to mutable values (worst case: just
  > make a copy).
  >
  > The problem with the current const iteration is that there is no way to
  > separate the orthogonal concepts constness (as in constants) from
  > read-only access of references.
  >
  > Also, as far as I can see (not very) there would also not be any need
  > for a third type of constant (the manifest one).
  >
  > const int x = 5;
  >
  > could behave like a template and only be instantiated when the address
  > of x is taken.
  >
  > The result would be a const design, equally powerful but without
  > spoiling the beauty and simplicity of the D1.0 const. In D1.0, when
  > something is *constant*, you simply declare it as const:
  >
  > struct T {
  >     const int a = 5;
  >     int b;
  > }
  >
  > static assert(T.sizeof == int.sizeof);
  >
  > No need to stop and think. In D2.0 it seems like you have to go through:
  > "Hmm, this value will never change. I will mark it as const.... no wait
  > invariant... or maybe manifest constant using the enumeration hack?"
  >
 
 (Referring to http://www.csc.kth.se/~ol/const.pdf in comments below that 
 mention "your proposal")
 
 I think everyone has trouble seeing how your const proposal fixes the 
 tricky issues.  You give some syntaxes but don't show how they solve the 
 problems that have caused all the consternation (!) over the past year 
 that arise when you start pulling on the string. 

I can't say it fixes all tricky issues, and since I have no full grasp of what all the tricky issues are I regret calling it a proposal. Let's just consider it a thought experiment.
 Also your comparison 
 table doesn't include structs, which seems a big omission since structs 
 often end up causing the trouble with const proposals.  Also it would be 
 helpful if your table gave an brief description of the behavior of each 
 row: like "in int*: the pointer can be modified but the int cannot via 
 this pointer"

More on that below.
 But anyway, if I understand from the brief description, the idea is 
 basically to use "const" to apply to values only and to mean the value 
 will not change, period, no way, never.  And you use 'in' exclusively 
 for things that can refer to other memory to say "you can't change what 
 it refers to through this reference".
 
 Ok, that sounds pretty neat.

It does, doesn't it? My main gripe with the design of the current const is that the meanings of invariant and const are too overlapping (non-orthogonal if you wish). A plain int can have three different types: int const int invariant int Where the latter two are almost (but not fully) identical. There are other ways to resolve this, but for now I will focus on orthogonalizing the concepts. Plain old data would only come in two flavors: variable and constant. The "plain" data types are: * all primitive non-reference types (int,float,char,...) * structs and unions containing only plain data types.
 So first question is: what happens when you use "in" on a plain int?  It 
 isn't a reference of any kind so is "in int" an error or a no-op?

Both behaviors are possible, but I think making it a no-op is the most helpful one. The result in either case is that nothing could ever have the type "in int".
 Does it have to be "const const(int)*"?  Seems like that should be the 
 same as "const(int*)".  It's unclear in your proposal how const and in 
 are scoped.

If const only applies to the actual data without imposing any contractual limitations on what may be done with eg. a copy of the data we gain the neat effect of always having const T implicitly convertible to T. From this also follows that const (as opposed to in) has to be intransitive which is why I wrote "const const(int)*". We could also consider a design where constness imposes contractual limitations on the data, eg. making const fully transitive, but we would lose the implicit const T => T conversion. This is outside the scope of this thought experiment, but nevertheless leads down some interesting paths. (*)
 I see some potential messiness given a pointer variable t of type 'T' 
 determining whether I can modify what it points to or not.  As far as I 
 can tell, T can come in 3 non-modifiable flavors:
 * "in S*" (readonly ref to mutable S),
 * "in const(int)*" (readonly ref to immutable S),
 * and "const(int)*" (mutable ref to immutable S)
 So i'd need to check if either "typeof(*t) is const" or "typeof(t) is in".

The "in" attribute of a reference could be implicit from having a reference to constant data. So typeof(const(T)*) == in const(T)* and you'd only need to check the in attribute of the type to know if you can modify what it refers to. Regarding in and structs, I think it is advantageous if T[] and struct Slice(T) { T *ptr; size_t length; } behaved identically with respect to a const T and an in type attribute which would have certain implications.
 Hope my questions are clear enough to you and give you some insight into 
 what parts of your proposal people find difficult to understand.

Thank you for the questions. They were good, although not as tricky as I feared (there exists tricky ones). :) I hope I managed to answer them and bring some clarity on the topic. At least, I hope this may possibilities forward for the current debacle. -- Oskar
Jan 05 2008