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









=?ISO-8859-1?Q?S=F6nke_Ludwig?= 