digitalmars.D - Yet Another Const Proposal
- =?ISO-8859-15?Q?S=F6nke_Ludwig?= (187/187) Dec 07 2007 (WARNING: LONG POST AHEAD)
- Janice Caron (6/7) Dec 07 2007 Shouldn't that be
- =?ISO-8859-1?Q?S=F6nke_Ludwig?= (2/13) Dec 07 2007 Of course! Should have checked the code before posting..
- =?ISO-8859-15?Q?S=F6nke_Ludwig?= (3/3) Dec 07 2007 I should add what the intention was for going this direction. The main b...
- Janice Caron (18/21) Dec 07 2007 He's got a point. Here's a more drastic example.
- Janice Caron (6/7) Dec 07 2007 I have no suggestions, and haven't spotted any mistakes. In fact, good
- =?ISO-8859-1?Q?S=F6nke_Ludwig?= (13/23) Dec 07 2007 My main (only?) concern are tail-const classes. If they had a transparen...
- Walter Bright (2/23) Dec 08 2007 I hadn't thought of that. Good catch.
- Janice Caron (16/19) Dec 08 2007 Yes, I had it in my head that TailConst! and TailInvariant! would be
(WARNING: LONG POST AHEAD) After numerous suggested const-designs, of which none was fully convincing, I'd like to make another suggestion. First the latest suggestion made by Walter: ======================= == latest proposal == ======================= head tail decl meaning -------------------------------------------------------------------------------- 1 mutable mutable T t t fully modifyable 2 mutable const TailConst!(T) 3 mutable invariant TailInvariant!(T) 4 const mutable -- 5 const const const(T) t t not reassignable, members const 6 const invariant -- 7 invariant mutable -- 8 invariant const -- 9 invariant invariant invariant(T) t t not reassignable, members invariant with const T <=> const(T) This system, while having a simple and consistent syntax, is basically also really limited in its abilities. The important cases (2) and (3) are actually not possible to do transparently _today_. Also (IMO) these cases are so basic that they deserve to be possible in the core language. Also, an important observation: ---- class C {} struct S { invariant(C) inst; } S s = S(new C); invariant(C)* pc = &s.inst; s = S(null); assert( *pc != null ); // fails // *pc is null now, despite beeing invariant(C)*, which should // mean, that the memory pointed to by pc will never change! ---- So to make the system semantically consistent, you'd have to forbid structure assignments, if there are any invariant members. This in turn makes the TailInvariant!()-solution impossible without severly hacking around the type system. ==================== == new proposal == ==================== My solution now would use basically the same syntax, as in 2.008 - but instead of specifying a storage class with 'const X', specify head-constness. Transitivity still holds: if X is head const, it is also tail const: const T <=> const const(T) The cosmetic problem of const T not beeing equivalent to const(T) remains, of course. head tail decl meaning -------------------------------------------------------------------------------- 1 mutable mutable T t t fully modifyable 2 mutable const const(T) t assignable, tail const 3 mutable invariant invariant(T) t assignable, tail invariant 4 const mutable -- 5 const const const T t head const, tail const 6 const invariant const invariant(T) t head const, tail invariant 7 invariant mutable -- 8 invariant const -- 9 invariant invariant invariant T t head invariant, tail invariant [case (6) is there for completeness, but normally (9) would be used instead] So this gives at least a semantically consistent system. But of course you still have to learn the difference between 'const C' and 'const(C)'. Some usage examples: Class/Struct ============ S s: s = t; // ok s.x = 1; // ok typeof(&s): S* const(S) s: s = t; // ok s.x = 1; // error typeof(&s): const(S)* const S: s = t; // error s.x = 1; // error typeof(&s): (const S)* Primitives ========== Same as structs/classes, just without the member access. int i: i = 1; // ok i++; // ok typeof(&i): int* const(int) i: i = 1; // ok i++; // ok, handled as reassignment (debatable) typeof(&i): const(int)* const int i: i = 1; // error i++; // error typeof(&i): (const int)* Array ===== T[] a: a = b; // ok a ~= t; // ok a[0] = t; // ok a[0].x = 1; // ok typeof(a[0]): T foreach( ref x; a ): typeof(x): T const(T)[] a: a = b; // ok a ~= t; // ok a[0] = t; // ok a[0].x = 1; // error typeof(a[0]): const(T) foreach( ref x; a ): typeof(x): const(T) (const T)[]: a = b; // ok a ~= t; // ok a[0] = t; // error a[0].x = 1; // error typeof(a[0]): const T foreach( ref x; a ): typeof(x): const T const(T[]) a: a = b; // ok a ~= t; // error a[0] = t; // error a[0].x = 1; // error typeof(a[0]): const T foreach( ref x; a ): typeof(x): const T const T[]: a = b; // error a ~= t; // error a[0] = t; // error a[0].x = 1; // error typeof(a[0]): const T foreach( ref x; a ): typeof(x): const T Reference parameters ==================== These are exactly the same, as non reference parameters: ref S s: s = t; // ok s.x = 1; // ok typeof(s): S typeof(&s): S* ref const(S) s: s = t; // ok s.x = 1; // ok typeof(s): const(S) typeof(&s): const(S)* ref const S s: s = t; // ok s.x = 1; // ok typeof(s): const S typeof(&s): (const S)* Grammar ======= Just to see if there might be any ambiguities I've also made a matching grammar. When building a type, invariant always transitively overrides const - but not the other way around. TypeDecl -> HeadConstTypeDecl HeadConstTypeDecl -> ConstTypeDecl -> const NonConstTypeDecl -> invariant NonConstTypeDecl ConstTypeDecl -> NonConstTypeDecl -> const(NonConstTypeDecl) -> invariant(NonConstTypeDecl) NonConstTypeDecl -> PrimitiveTypeDecl -> PrimitiveTypeDecl[] -> (HeadConstTypeDecl)[] -> PrimitiveTypeDecl* -> (HeadConstTypeDecl)* PrimitiveTypeDecl: -> void | int | ... -> Identifier Of course, some redundant declarations are possible, not sure if this is a problem (it's acually neccessary to allow this on a semantic level to transparently work with aliases): const (const (const T)*)* <=> const T** I'd be glad to hear any suggestions or mistakes I may have made. Sönke
Dec 07 2007
On Dec 7, 2007 3:20 PM, Sönke Ludwig <ludwig informatik_dot_uni-luebeck.de> wrote:assert( *pc != null ); // failsShouldn't that be assert(*pc !is null) ? ( *pc != null ) will attempt to call (*pc).opCmp(), and will fall over if (*pc) is null.
Dec 07 2007
Janice Caron schrieb:On Dec 7, 2007 3:20 PM, Sönke Ludwig <ludwig informatik_dot_uni-luebeck.de> wrote:Of course! Should have checked the code before posting..assert( *pc != null ); // failsShouldn't that be assert(*pc !is null) ? ( *pc != null ) will attempt to call (*pc).opCmp(), and will fall over if (*pc) is null.
Dec 07 2007
I should add what the intention was for going this direction. The main benefit is that this method is backwards compatible to the old system and to the 2.008 system and does not introduce breaking changes.
Dec 07 2007
On 12/7/07, Sönke Ludwig <ludwig informatik_dot_uni-luebeck.de> wrote:Also, an important observation:<snip>So to make the system semantically consistent, you'd have to forbid structure assignments, if there are any invariant members.He's got a point. Here's a more drastic example. class A { invariant(char)[5] a; } A a; a = A("hello"); string s = a.a; writefln(s); // prints "hello" a = A("world"); writefln(s); // prints "world" Whoops! The assignment of a seems to modified s ... an invariant! I haven't actually tried to compile this, so it might not work. (Anyone want to try it?). But Sönke's rule is sound: if a struct contains an invariant (or, I would add, const) member, then assigning the struct should be ruled out.
Dec 07 2007
On 12/7/07, Sönke Ludwig <ludwig informatik_dot_uni-luebeck.de> wrote:I'd be glad to hear any suggestions or mistakes I may have made.I have no suggestions, and haven't spotted any mistakes. In fact, good catch on the invariant overwriting problem! But I have to ask - what's the problem with Walter's latest suggestion? It seems absolutely perfect to me. I can't fault it. What do think it doesn't do, that we need?
Dec 07 2007
Janice Caron wrote:On 12/7/07, Sönke Ludwig <ludwig informatik_dot_uni-luebeck.de> wrote:My main (only?) concern are tail-const classes. If they had a transparent syntax, I wouldn't care for them to be a special case. But using a template makes it feel strange - but that can probably be resolved using macros [TailConst!(X) -> tailconst(X)]. Another problem with templates is, that it is currently not possible to transparently wrap a type in a struct. But that should be resolved at some point, so it should only be a temporary problem. The second thing is that I think from a language point of view it's unclean to not be able to provide such a typing-feature without bypassing the type system (casting away the const/invariantness at some point). However, if only point 2 remains, I think, from a users point of view, such a system would probably be as clean as it would get. So actually, I'm not really sure which one I would prefer right now <g>.I'd be glad to hear any suggestions or mistakes I may have made.I have no suggestions, and haven't spotted any mistakes. In fact, good catch on the invariant overwriting problem! But I have to ask - what's the problem with Walter's latest suggestion? It seems absolutely perfect to me. I can't fault it. What do think it doesn't do, that we need?
Dec 07 2007
Sönke Ludwig wrote:Also, an important observation: ---- class C {} struct S { invariant(C) inst; } S s = S(new C); invariant(C)* pc = &s.inst; s = S(null); assert( *pc != null ); // fails // *pc is null now, despite beeing invariant(C)*, which should // mean, that the memory pointed to by pc will never change! ---- So to make the system semantically consistent, you'd have to forbid structure assignments, if there are any invariant members. This in turn makes the TailInvariant!()-solution impossible without severly hacking around the type system.I hadn't thought of that. Good catch.
Dec 08 2007
On 12/7/07, Sönke Ludwig <ludwig informatik_dot_uni-luebeck.de> wrote:This in turn makes the TailInvariant!()-solution impossible without severly hacking around the type system.Yes, I had it in my head that TailConst! and TailInvariant! would be impossible without casting away const somewhere along the line. I got temporarily distracted when Walter said it could be done without casting away const by wrapping the class in a struct. I should have spotted then that const-correctness was being violated, but I wasn't paying enough attention. I don't necessarily think this is a problem though (although overwriting const data obviously /is/), because TailConst! and TailInvariant! would be library-supplied templates, written by people who completely understood what was happening "under the hood", and one would assume that writers such as Walter or Andrei could write them in a "safe" way. (I'm sure I could have a bash myself, for that matter). But yeah - the assignment of structs which contain const or invariant members, is a violation of const correctness, and I believe it needs to be fixed.
Dec 08 2007