| Thread overview | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
March 24, 2008 Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
This idea has come from the discussion on the const debacle thread.
It is basically an idea for scoped const. The main goal is so that one can specify that a function does not modify an argument without affecting the constness of the input.
The main problem to solve would be that I have a function with an argument that returns a subset of the argument. The easiest function to help explain the problem is strchr. Please please do NOT tell me that my design is fundamentally unsound because you can return a range or pair, and then slice the original arg based on that pair. There are other examples that cannot be solved this way, this is just the easiest to explain with. Everyone who uses C should know about strchr:
char *strchr(char const *source, char const *pattern);
The result of strchr is meant to be a pointer into source where pattern exists.
Note that this is not even close to const-correct in C, because if you pass in a const source, the const is inhernetly cast away.
So let's move to the D version, which I'll specify with const to begin with:
const(char)[] strchr(const(char)[] source, const(char)[] pattern);
Note that const(char)[] MUST be the return value, because otherwise we cannot return a slice into source. So far so good, but now, if I am using strchr to search for a pattern in a mutable string, and then I want to MODIFY the original string, I must cast away const, because the return value is const. OK, so you might say let's add an overload (or templatize strchr):
char[] strchr(char[] source, const(char)[] pattern);
Which compiles and works, but I cannot specify with the signature that source will not be modified. Therefore, the compiler is not able to take advantage of optimizations, and the caller is not guaranteed his source array will be untouched.
So, how do we specify this? I propose a keyword is used to specify "scoped const", which basically means, "this variable is const within this function, but reverts to it's original const-ness when returned", let's call it foo (as a generic name for now):
foo(char)[] strchr(foo(char)[] source, const(char)[] pattern);
Note that foo only specifies source and not pattern because we are not returning anything from pattern, so it can be fully const.
What does this mean? foo(char)[] source is not modifiable within strchr, but is implicitly castable to the type of the argument at the call site. So if we call strchr with a char[], foo(char)[] is essentially an alias to const(char)[] while inside strchr, but upon return is implicitly castable back to char[]. This does not violate any const contracts because the argument was mutable to begin with. If we call strchr with a const(char)[], foo(char)[] cannot be implicitly cast to char[] because the call site version was not mutable, and implicitly removing const would violate const rules. These rules can easily be checked by the compiler at the call site, and so the function source does not need to be available.
So why must we have a keyword specification? Because of the expressive nature of const types, you must be able to match exactly where the const comes into play. For example, const(char)* is different than const(char*), and so foo must be just as expressive. And in addition, the type returned may not be exactly the parameter passed in, but the const-ness must be upheld.
For example, what if the argument was a class, and the return type was unrelated:
foo(membertype) getMember(foo(classtype ct)) { foo(membertype) return ct.member;}
Note that if member is a function, it must also be foo, or else the contract could be violated.
You should be able to declare intermediate variables of type foo(x):
foo(membertype) = ct.member;
What if there are multiple arguments, and the result may come from any of them:
foo(T) min(T)(foo(T) val1, foo(T) val2);
what if one calls min with a mutable, and an invariant type? The answer is that foo should map to the least common denominator. If all of the foo's are identical (invariant, const, or mutable), then the resulting foo would be identical. If any of them differ, the resulting foo must be const to uphold const-correctness.
In any case, val1, and val2 are const for the body of the function.
There are other benefits. For example, to implement a min function that allows a mutable return for mutable arguments, you must define min as a template, which can generate up to 6 variations (for all the different argument const types), but with the foo notation, the function generated is always identical. The only check for const-correctness is at the call site.
Note that this idea is very similar to Janice's idea of:
K(T) f(const K, T)(K(T) t);
The differences are:
- This idea does not require a different template instantiation for
identical code, and in fact is not a template, so it does not require source
or generate bloat.
- This idea ensures that the argument remains const inside the function
even if the argument at the call site is mutable. It enforces the contract
that the caller is making that the argument will never be modified inside
the function.
------------------- PROPOSAL FOR KEYWORD -------------------
That is my general proposal for scoped const, and as an orthogonal suggestion, which should by no means take away from my above proposal, I suggest we use the argument keywords 'in' and 'out' to specify foo:
out(char)[] strchr(in(char)[] source, const(char)[] pattern);
So arguments are implicitly castable to 'in', no matter if they are mutable,
const, or invariant.
'in' types are implicitly castable to 'out' types.
'in' arguments cannot be modified inside the function (i.e. they are
essentially const, but with the additional specification that they can be
cast to 'out').
'out' is an alias for the constness at the call site defined by the
following rules:
- if all of the 'in' parameters are of one constancy, (i.e. all are
mutable, all are invariant, or all are const), then out is defined to be the
same constancy.
- if there are two different constancy values for 'in', then 'out' is
defined to be const.
These type declarations are made at the call site, not inside the function.
The function is compiled the same for all versions of 'in' and 'out'.
And for functions that are members of a class:
in out(T) func() {...} // essentially, in(this)
or
out(T) func() in {...}
Rationale: I think in and out are pretty much defunct keywords in this context (out replaced by ref, in replaced by const), and so are fair game for this syntax. They are also very good english descriptions of what I am trying to do.
-Steve
| ||||
March 24, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | "Steven Schveighoffer" wrote in message
> This idea has come from the discussion on the const debacle thread.
>
> It is basically an idea for scoped const. The main goal is so that one
> can specify that a function does not modify an argument without affecting
> the constness of the input.
> ...
I have thought of an additional specification that I've left out, regarding calling functions from within a scoped const function.
If a function with scoped const calls another function, the following rules apply:
- if the second function is a scoped const function, and the parameters
passed to the scoped const function are declared with 'in', then the
resulting 'out' of the second function can be returned from the outer
function, or can be assigned to another 'in' variable.
- if the second function is not a scoped const function, or the parameters
passed to the second function are not declared with 'in', then the result of
the second function cannot be assigned to an 'in' variable.
For example:
out(T) g(in(T) t){...}
const(T) h(const(T) t) {...}
out(T) f(in(T) t)
{
t = g(t); // OK, g is scoped const
in(T) a = g(t); // ok, assigning to scoped const variable
const(T) b = g(t); // ok, in(T) is implicitly castable to const.
in(T) c = h(t); // Error, cannot implicitly cast const to in.
in(T) d = h(b); // Error, b is not in(T), so the result is the type of b,
even if f was called with a const(T)
const(T) d = h(t); // OK, can cast in(T) to const(T) before calling h
return a; // OK
}
-Steve
| |||
March 24, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | "Steven Schveighoffer" wrote
> This idea has come from the discussion on the const debacle thread.
>
> It is basically an idea for scoped const. The main goal is so that one can specify that a function does not modify an argument without affecting the constness of the input.
>
> The main problem to solve would be that I have a function with an argument that returns a subset of the argument. The easiest function to help explain the problem is strchr. Please please do NOT tell me that my design is fundamentally unsound because you can return a range or pair, and then slice the original arg based on that pair. There are other examples that cannot be solved this way, this is just the easiest to explain with. Everyone who uses C should know about strchr:
>
> char *strchr(char const *source, char const *pattern);
GAH!!! I meant this to be strstr. Sorry everyone, please assume I meant strstr everywhere I wrote strchr.
-Steve
| |||
March 24, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | Steven Schveighoffer wrote:
> It is basically an idea for scoped const. The main goal is so that one can specify that a function does not modify an argument without affecting the constness of the input.
I understand what you're asking for, and it does solve some issues. It's almost exactly the same as the "return" qualifier I'd bandied about last summer:
T foo(return T t);
where the 'constness' of the argument for t is transmitted to foo's return type at the point of call of foo(), not at the point of definition of foo. This implies, of course, that foo cannot change t itself.
For me, the question is is solving these issues a large enough problem that justifies adding a rather confusing new variation on const?
| |||
March 24, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 24/03/2008, Walter Bright <newshound1@digitalmars.com> wrote:
> For me, the question is is solving these issues a large enough problem
> that justifies adding a rather confusing new variation on const?
We can trade! :-)
We could drop "in" as a function parameter attribute. I find the difference between "in" and "const" to be somewhat unfathomable. Removing "in" would simplify the const system, with, as far as I can see, no real loss.
See - you can add variations with one hand, and take them away with the other! :-)
However, I can see a problem with the "return" syntax, exemplified by the following example.
const(char)[] g = "hello"; // global variable
char[] f(return char[] s)
{
return g;
}
char[] s;
s = f(s);
s[0] = 'x';
It's not just the constancy you have to worry about. You also have to prove that you are actually returning what you claim you're returning. As in, the actual variable. That's why I earlier suggested
sliceof(t) foo(const(T) t)
...although "sliceof" is clearly the wrong word, since [] is not defined for all types.
| |||
March 24, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
That said, for min(), you'd want to return /any/ of the input parameters, so I guess that means your syntax is better than mine. | ||||
March 24, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | "Walter Bright" wrote > Steven Schveighoffer wrote: >> It is basically an idea for scoped const. The main goal is so that one can specify that a function does not modify an argument without affecting the constness of the input. > > I understand what you're asking for, and it does solve some issues. It's almost exactly the same as the "return" qualifier I'd bandied about last summer: > > T foo(return T t); > > where the 'constness' of the argument for t is transmitted to foo's return type at the point of call of foo(), not at the point of definition of foo. This implies, of course, that foo cannot change t itself. This is sort of a subset of what I am proposing, as for my solution, the return type may be not just of type T, but must be of the same constness. This is important if you have a function that returns a member of a struct or class, that you want to carry the same constness factor (think properties). > > For me, the question is is solving these issues a large enough problem that justifies adding a rather confusing new variation on const? To me, many functions that would be a good candidate for const are now removed from being fully-const. I have mentioned in my post strstr, and min/max. There are a whole slew of functions that would benefit, but must now be either re-implemented for each version of const, or cannot be implemented with const at all. Think of properties. Only one function is required for const, invariant, and mutable. Think of text processing, only one function for all these constancies, plus the mutable versions can return mutable values while promising that the function is const. Without this, string processing functions that take 2 arguments would generate potentially 3^2 x 3 = 18 variations, the 3^2 being const invariant and mutable for each argument independently, and the x 3 factor for char, wchar, and dchar. With this change, only 3 functions for char, wchar, and dchar. This can also have optimization and functional programming benefits. As for being confusing, I don't see it to be any more confusing than const or mixin was to me to begin with. Like any feature that does not have a precedent, this will take some learning, but the resulting code will be so much more maintainable, readable, and functional. I am biased of course, since I proposed the idea :) But I know of at least one other person that thinks this is needed. Couple that with the fact that the 'in' parameter syntax already exists! Just add the 'out' for the return value, and allow 'in' variables. IMO, 'in' and 'out' are very understandable, already used words in programming to mean 'input only' and 'output only'. Well, I guess 'out' in this context doesn't really mean that, but it is necessary to have a keyword to show how const would apply to the output type in case it doesn't match the input type. Like I said in my original post, any keyword is fine with me as long as the functionality is there. If at the end of having const permeated throughout the language, nobody uses it because it's too unwieldly, then it's not a good feature, no matter how many theoretical problems it solves in special case programming. -Steve | |||
March 24, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Walter Bright | On 24/03/2008, Walter Bright <newshound1@digitalmars.com> wrote:
>
> T foo(return T t);
Actually, I do find that confusing, because any return statement inside the body of the function would have to return a const(T), not a T. Watch:
T foo(return T t)
{
T u = t; /* Whoops - won't compile */
const(T) u = t; /* OK */
return u;
}
It would be less confusing if it were declared as
const(T) foo(return T t)
| |||
March 24, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | "Steven Schveighoffer" wrote
> Without this, string processing functions that take 2 arguments would generate potentially 3^2 x 3 = 18 variations, the 3^2 being const invariant and mutable for each argument independently, and the x 3 factor for char, wchar, and dchar.
OK, first things first, I need to get my basic math skills right :)
that should have read *27* variations.
-Steve
| |||
March 24, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | Steven Schveighoffer Wrote:
> So, how do we specify this?
I'm not a D 2.x user yet, but wouldn't it simply be the following?
char[] strchr(return char[] source, const char[] pattern);
Note that pattern can always be const by the very nature of the function. Only the source argument needs to be coupled with the return type. I think this compactly reduces the various const forms into a simple and easy to define function. Having the return type be naked kind of bugs me a bit, but it if I can get away with writing just one function like that, I'm all for it.
| |||
Copyright © 1999-2021 by the D Language Foundation
Permalink
Reply