YACP -- Yet Another Const Proposal
Reiner Pope
reiner.pope at gmail.com
Mon Jul 31 00:25:17 PDT 2006
Thank you for your comments. Certainly, the proposal is only started,
but much of it, I believe, can also be gleaned from how Javari manages
it. You also raised concerns which I was not aware of, which is why it
is so important that many people discuss it.
Bruno Medeiros wrote:
> This might have not been the best time to deal with this. This is a
> complex issue, people are very busy and the dust is just settling with
> regards to the import issues.
I don't understand how that causes such problems, but I'm also not
pushing for anything yet. Let's work out the details first, and then
start convincing people it's right. :-)
> Reiner Pope wrote:
>>
>> *1. Mutability vs assignability*
>
> I think that as a simplification, immutable variables should also be
> unassignable. That is, all readonly vars would be final as well. I have
> a feeling this would be beneficial.
Look at stepping through a linked list:
void stepThrough()
{ readonly /*assignable*/ currentNode = foo();
while (currentNode.next) currentNode = next;
// currentNode is now the last node
}
We don't want to modify currentNode, because we want to keep the list
structure the same. However, we do want to step through it. For cases
like this, I think it is important
>> *3. rocheck*
>>
>
> I haven't read that part of the paper yet, but I think I get a good
> enough picture from your description.
> And I think rocheck wouldn't fit well with D, due to the runtime checks
> and performance penalties, which, like you said, could not be disabled
> in release builds (as they are not contracts). Also, it would only work
> with classes, and not any kind of type.
> (You have an example where you have a "rocheck char[]" variable, how
> would that work internally?)
This isn't in the paper. It's my idea, arising from some of the problems
with getting CoW to work properly. Consider:
char[] /*someCOWFunction*/ foo(char[] input) {}
char[] /*Also COW*/ bar(char[] input) {}
char[] /*Also COW*/ bam(char[] input) {}
foo(bar(bam("hello world")));
Each of them would ordinarily duplicate the string if a modification is
required. However, a maximum of 1 is needed in real life, because no-one
owns the intermediate copies. So there needs to be some way to know at
runtime whether the array reference is readonly or not, because if it is
mutable, then the function would be best advised to modify it in-place.
That's what rocheck is for. However, in my proposal, it is both:
-statically verifiable as correct
-devoid of runtime checking
this is because the mutating methods can only be accessed via the
ensureWritable() method generated by the compiler, so a correct COW
function would look like this:
rocheck char[] capitalizeAllIfFirstLetterIsLowerThenRunBar(rocheck
char[] input)
{ if (isLowerCase(input[0]))
{ /*mutable*/ char[] modified = input.ensureWritable();
foreach (inout c; modified)
{ c = makeUpper(c);
}
input = modified;
}
bar(input);
}
So in this function, it is clearly const-safe as well as only doing one
check in the entire function (the call to ensureWritable). This means
that, although there is a memory cost added to the array reference,
there is actually a runtime saving because of the avoidance of
duplications. Additionally, since using this requires a special keyword,
it will only be used where the memory/speed tradeoff is deemed
worthwhile by the programmer (however, I recommend that it is used for
any COW function).
>> *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).
>>
>
> I don't like to think of a immutable type as a supertype of a mutable,
> although the semantics are the same.
Why not?
Obviously, we can choose to think about things as we wish, but if the
semantics are simplest to describe this way, then what's wrong with
that? Even if it is 'conceptually' wrong, the simplest explanation has
its merits for just that reason.
>
>>
>> *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
>
> No it wouldn't guarantee any of that (no side-effects). The function
> could access global variables, static variables, other functions, etc.
> (there are many things other than just the functions parameters)
>
Ok, I didn't mean to use the exact same meaning as the meaning which
exists in classes. I thought that utility functions tend not be
contained within classes, but rather as global functions. I meant
overloading the const function declaration to mean the following for
global functions:
they don't access any global/static variables other than some sort of
mutable exception (just like const functions in classes being able to
access mutable fields). However, this restriction would be even greater,
because they shouldn't even read them, so the result can't be affected
by an external function. However, I believe this won't break any code,
so further thought about this can afford to be delayed. However, the
potential for optimizations is promising, so I think we should consider it.
> ----
>
> *Overall Comments*
>
> This seems like a good starting point for a const proposal. But we have
> to think of all aspects of how this would work with D. D is more complex
> than Java, and there are more things to consider when implementing
> immutability. In particular D is way more advanced in features that work
> with types, like templates, template specializations, typeof
> expressions, is expressions, etc., and we have to think how const would
> work with those, and see if it is all correct, solid and feasible, (and
> also if there is a better way).
Ok, I have not considered any of these. Time to learn up on that, obviously.
> How to use type inference to create a readonly view of some mutable data?
> immutable auto bar = somefoo.somebar; ?
> auto bar = cast(immutable) somefoo.somebar; ?
yep, something like that. Or maybe a syntactical shorthand:
somefoo.immutable
will always return the reference cast to immutable
>
> Would "typeof(foo)" include the constness of foo?
> (Hum...)
I have no idea. I've never used 'typeof', so I can't really consider the
use cases right now.
>
> Should another is-expression metatype check be added? How would it work?
> is(constof(foo) == immutable) ?
> is(typeof(foo) == immutable) ?
> (Hum...)
Something like this. Again, I haven't used this, and I also can't find
it on a preliminary search of the documentation. However, I think the
choice isn't that difficult, and it would be a simple matter of deciding
what is most consistent with D (which other people are better qualified
to do).
>
> How would template specialization work with constness?
> (T : Object) -> specializes on any Objects or just mutable Objects?
> (T : T*) -> specializes on any pointers or just mutable ones?
>
> How to specialize on just mutable or immutables types?
> (T : mutable Object) ?
> (T : immutable Object)?
> (as we can we probably we also need would a "mutable" keyword)
>
Those two things you mentioned there seem to be the same. I see no point
in specializing just to immutable Objects, since we've already said that
mutable objects could be implicitly cast to immutable. However, there
should be a form of specialization the other way. Javari suggests is this:
(T : Object) -> The object must derive from Object, which is mutable.
Therefore, T must be mutable
(T: readonly Object) -> The object must derive from readonly Object.
Therefore T can be either immutable or mutable.
I don't like this, however, because it implicitly restricts T: Object to
mutable Objects, whereas I think that restrictions should be explicit.
Const by default should do the trick: we would get the following:
(T: Object) -> Both mutable and immutable allowed
(T: mutable Object) -> only mutable allowed
>
> As you see there are still many details that must be considered. I think
> this is by far the most complicated proposal D will ever face.
>
It may well be, (not that I am one to judge) which is why I appreciate
other people's input so much. I don't feel possessive of this: I just
want a good solution to emerge.
Cheers,
Reiner
More information about the Digitalmars-d
mailing list