YACP -- Yet Another Const Proposal

Reiner Pope reiner.pope at gmail.com
Thu Jul 27 13:48:37 PDT 2006


Ugh, much as I hate more and more proposals over the same thing, if 
enough people have enough different ideas, we might eventually get 
somewhere. If anyone is interested in these ideas, please feel free to 
elaborate into an *even* better system, or just say that you like it, or 
whatever.

This proposal mostly involves introducing Javari's ideas to the 
discussion, as well as how they integrate into D. The paper goes into 
much more detail, and you can get it at 
http://pag.csail.mit.edu/pubs/ref-immutability-oopsla2005-abstract.html 
(or just google 'Javari'). I highly recommend reading it, because it is 
very informative and I refer to it in this post.

I give some more code examples as well as reasons why it satisfies 
Walter's objections in subsequent posts.

The important aspects of my proposal are:
1. using Javari's distinction between mutability and assignability
2. enforcing const by default for method 'in' parameters.
3. introducing an in-between type known as rocheck (readonly, checked), 
which keeps track of const-ness at runtime, for things like copy on 
write, as I discussed in other posts
4. allow readonly templating, a la Javari (which avoids code duplication 
for some const methods)
5. allow type inference using 'auto' to also detect const-ness.

In slightly more depth:

*1. Mutability vs assignability*
_Assignability_: the property which determines whether a variable can be 
used as an Lvalue.

_Mutability_: this determines whether alterations are permissible to the 
logical and /abstract/ state of an object (from the Javari paper, 'the 
abstract state is (part of) the transitively reachable state: that is, 
the state of the object and all state reachable from it by following 
references').

This clears up the need for C++'s things like:
   const char const * foo;
which would instead turn into the more understandable:
   readonly final char[] foo;
using the syntax from Javari (syntax is not a major concern of mine, 
though).

Obviously, since Javari is based on Java, pointers are not an issue. 
However, it turns out that this fits perfectly with the 
assignability/mutability distinction. The following law is added: a 
pointer to an unassignable and/or immutable object must be immutable.

*2. Const by default*
'Const by default', as Walter discussed, seems like a very useful 
detail, and I suggest it would be included. This would mean that all 
unmarked parameters in functions are truly 'in', which means immutable 
for reference types (pass-by-value types are already fine). This is 
already done to an extreme in functional languages, where the 
fundamental idiom is that *everything* is const. However, that means 
in-place modification is impossible, so I see const by default as a 
happy medium. Const by default in function parameters means:
-less typing, saving time, and also making it more likely that libraries 
are const-aware.
-it probably leads to the least broken code, because most functions 
don't modify their parameters anyway.

In code, it means that this C++ code:
   void foo(const char const * string) {...}
would look like this in D:
   void foo(char[] string) {...} // Since string is an 'in' parameter, 
it can't be modified

*3. rocheck*
A detail which Javari introduces is casting to mutable from immutable. 
This works around the static const-checking system, but safety is 
'ensured' by inserting dynamic checking everywhere. I wondered how this 
was implemented efficiently. Another paper indicates that this requies 
inserting a const-violation check at _every modification_ of _every 
mutable variable_. Despite the claims that this only leaves a 10% (and 
with optimizations, less) overhead in Java, I see this overhead as way 
too much for a systems language like D, especially considering that it 
probably couldn't be disabled for release builds, because that would 
cause ABI incompatibilities between release and debug builds.

The two main reasons that the paper presents for casting to mutable are 
interfacing with const-unware code and the situations in which static 
checking is limited (see my other posts to find about these limitations: 
basically, they mean that extra duplications will be required, 
especially with copy-on-write things like string functions, toupper, etc).

As I discussed earlier, legacy code problems are ameliorated by 
const-by-default. The compiler limitations can be worked around by 
introducing a new type: rochecked (possibly readonly, but only known at 
runtime). Both mutable and const types can be cast to rochecked, and 
mutability is determined at runtime (by adding a field to the 
_reference_) but rocheck is in fact statically-verifiable as const-safe: 
it presents the same interface as an immutable type, but with two added 
methods:

isMutable() and

/*mutable*/ Type ensureWritable()
{   if (isMutable()) return cast(mutable) this; // cast(mutable) is only 
accessible by the compiler
     return this.dup;
}

Since the only access to the mutating methods are through the 
ensureWritable() method (which is inserted by the compiler), this is 
guaranteed not to modify the original object, and runtime /checking/ can 
be avoided to some extent.

*4. rotemplate/romaybe*
Javari introduces this as romaybe, but I think the keyword rotemplate is 
more informative. Anyway, let me describe. Consider the following C++ code:

class MyVector<Value>
{   Value* array;
     const Value getAtIndex(int index) const
     {   return array[index];
     }

     Value getAtIndex(int index)
     {   return array[index];
     }
}

The code for both functions is the same, but they are both required. 
Readonly templating turns it into the following equivalent D code:

class MyVector(Value)
{   Value[] array;
     romaybe Value opIndex(int index) romaybe
     {   return array[index];
     }
}

*5. const type inference*
This is just a mechanism for avoiding even more const attributes 
scattered throughout the code. Since const is just an alteration to the 
typing mechanism (at least, in Javari it is), type inference could be 
used to change this code:
   readonly char[] c = getSomeReadonlyFoo();
into
   auto c = getSomeReadonlyFoo();

*Everything else*
Static const-checking is done just as Javari, which means through the 
type system, in which every immutable type is a superclass of a 
corresponding mutable type, and casting is then allowed /to/ const, but 
not /out of/ const (this operation must be done via duplication, or 
dirty assembler hacks -- however, there should be enough flexibility 
that workarounds are not required often).

The rest of the details are identical to Javari, including:
- the concept of this-assignability and this-mutability
- the assignability rules
- functions may be overloaded by const-ness
- romaybe as a const-overloaded template. I think that this name would 
be more intuitive if it were rotemplate, though
- dealing with templates/arrays: specifications such as readonly 
List(readonly Date); are required

*Side note*
One more (slightly unrelated) possibility that this allows is declaring 
utility functions as const, like toupper, etc. I haven't thought much 
about the details, but this could allow, given some planning, a system 
to ensure that a given function has no side-effects, which means it will 
always give the same result given the same parameters. This can lead to 
some interesting optimizations, but since this would seem not to require 
any breaking changes, it can be left until later. This is just another 
step which could allow D to have more of the features of a functional 
language, but without the slowness.



More information about the Digitalmars-d mailing list