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