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