March 24, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Jason House |
"Jason House" wrote
> 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.
This works for simple functions like the one you gave.
But there are other places where this does not work. For example, the min function would require 2 return parameters. If you wanted to return a member of a class that was an input value. This is really important with properties, because you are generally tagging the 'this' pointer for const variance, and so you most likely will not be returning an instance of a class from a member function, but rather a member.
-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);
In C++, assuming you meant strstr, this is:
char const * strstr(char const *source, char const * pattern);
I believe C++ has this right. I do not understand why D programmers want to return a non-const from a const input, even in the face of D slicing.
| |||
March 25, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Edward Diener | "Edward Diener" wrote > 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); > > In C++, assuming you meant strstr, this is: Yes, I did mean strstr, sorry about that. > > char const * strstr(char const *source, char const * pattern); > > I believe C++ has this right. I do not understand why D programmers want to return a non-const from a const input, even in the face of D slicing. According to online docs, it is sometimes const, sometimes not, sometimes both (2 versions, one in which both return and arg are const, one in which both are mutable). The problem with your version is that it is not very useful for mutable strings. If you wanted, for instance, to replace all instances of "hello" with "jello", in order to use strstr, you would need to either cast away const, or do pointer arithmetic with the result. -Steve | |||
March 25, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | The more I think about this, the more I find I have to agree with Stephen completely. I think I might be able to come up with a better syntax though.
Instead of
out(T) min(T)(in(T) x, in(T) y)
{
return x < y ? x : y;
}
which is what Stephen suggested, I think this works better:
inout(U=T) U min(T)(U x, U y)
{
return x < y ? x : y;
}
It's a pretty straightforward enhancement really. Just preceed a function definition with the attribute inout(U=T), and then within the function definition, all U's are in/out types. Here's the same idea used with strstr
inout(U=char) U[] strstr(U[]s, const(char)[] pattern)
{
int n = s.find(pattern);
return n == -1 ? null : s[n..$];
}
The reason I like this better is that it means we only have to define the "in/out" type once, instead of multiple times. Also - it means we don't have to ditch the existing uses of "in" and "out".
We can also use it for member functions, except with a slightly modified syntax
class String
{
inout(this) strstr(const(String) pattern)
{
/*...*/
}
}
I like the idea of only having to express the inout type only once per function declaration, and I like the idea of not having to completely change the existing meanings of "in" and "out" in a way which would break old code.
Another advantage of my syntax is that you could use inout(U=T) as an attribute to bracket multiple functions. e.g.
inout(U=char)
{
U[] strchr(U[]s, char c);
U[] strstr(U[]s, const(char)[] pattern);
}
Stephen, what do you think? Have I missed anything?
| |||
March 25, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Janice Caron | "Janice Caron" wrote
> The more I think about this, the more I find I have to agree with Stephen completely. I think I might be able to come up with a better syntax though.
>
> Instead of
>
> out(T) min(T)(in(T) x, in(T) y)
> {
> return x < y ? x : y;
> }
>
> which is what Stephen suggested, I think this works better:
>
> inout(U=T) U min(T)(U x, U y)
> {
> return x < y ? x : y;
> }
>
> It's a pretty straightforward enhancement really. Just preceed a function definition with the attribute inout(U=T), and then within the function definition, all U's are in/out types. Here's the same idea used with strstr
>
> inout(U=char) U[] strstr(U[]s, const(char)[] pattern)
> {
> int n = s.find(pattern);
> return n == -1 ? null : s[n..$];
> }
>
> The reason I like this better is that it means we only have to define the "in/out" type once, instead of multiple times. Also - it means we don't have to ditch the existing uses of "in" and "out".
>
> We can also use it for member functions, except with a slightly modified syntax
>
> class String
> {
> inout(this) strstr(const(String) pattern)
> {
> /*...*/
> }
> }
>
> I like the idea of only having to express the inout type only once per function declaration, and I like the idea of not having to completely change the existing meanings of "in" and "out" in a way which would break old code.
>
> Another advantage of my syntax is that you could use inout(U=T) as an attribute to bracket multiple functions. e.g.
>
> inout(U=char)
> {
> U[] strchr(U[]s, char c);
> U[] strstr(U[]s, const(char)[] pattern);
> }
>
> Stephen, what do you think? Have I missed anything?
First, I'll say that I'm not a huge fan of my out() syntax. However, I do like being able to tag exactly what is variably const on the arguments, and I think it is necessary to tag the output, otherwise you lose expressiveness and limit the syntax to certain types of functions.
So I'm glad that you are trying to find a better syntax, but I don't think this is it. First, you are only allowing one type to be considered 'in' and 'out', where it must be different for properties (in that the return type should be variably const based on the constancy of the 'this' pointer, but the return type usually is not the same type as the 'this' pointer). Second, I'd much rather have the 'char' in the argument declaration rather than aliasing it to some other symbol ('U') at the beginning. I actually think it's less clear the way you have it. Third, there is no way to declare another variable of the same constancy as the input but of a different type. In my scheme, in(x), means the type x that is somehow derived from the input. So at any time you can declare any type and tag it with 'in', which means it is based on the input (and therefore, should carry the same constancy).
I have found there are more issues that my solution doesn't solve exactly, so these need to be figured out.
For example, a linked list of mutable classes might be a template defined as:
LinkList(T)
{
void append(T t) {...}
}
for the append function, the t argument is not modified during the function, but the link list node added should be not const. So if a LinkList node is Node(T), then how do you write the signature and body of this function? Is it important to declare t as an 'in' parameter? I'm almost thinking that there is no real way to have the compiler be able to prove that an argument is not modified for all types of functions.
In addition, going back to the same example, append should only accept a mutable object for t, otherwise, it cannot construct a proper node to add to the list. So how is this information communicated to the compiler? If we use:
void append(in(T) t);
Then a const or invariant t could be passed to the function, which would be no good.
Is this even a concern that we should worry about?
-Steve
| |||
March 25, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Steven Schveighoffer | The following is some kind of superset of what I have proposed in [1]
Example for syntax (*):
char[] f(char[] p : const(char)[]) { }
^ ^
Least restrictive type Tmin |
Contract type Tcontract
Basic discussion:
a.) f may only compile if it accomplishes the contract regarding p.
b.) It is an error if Tmin is not implicitly castable to Tcontract.
c.) For the syntax regarding the contract I used ":" at the moment. For further syntax proposals, see (*)
d.) At the moment we may call the example f with parameters of type char[] and const(char)[]. const(char[]) is not covered by the contract and therefor may have to be rejected. More on that (**)
e.) It is still nothing said what the typeof(p) is inside the function. I assume it has to be the type of the passed parameter.
f.) If we want to return some slice of p, we get stuck again. Walter and Andrei have presented a solution in [2] up to some degree (***).
char[] f(return char[] p : const(char)[]) { return p[17..42]; }
g.) f can be virtual, because no templates are required.
If static type checking is used inside f we may come out with up to N possible binary versions of f, where N is the number of possible const-levels between Tmin and Tcontract. It is not 2^N because of transitive const. e.g.
void foo(char[][][][] x : const(char[][][])[]) {...}
may generate N=5 different versions up to:
const(char[][][])[],
const(char[][])[][],
const(char[])[][][],
const(char)[][][][],
char[][][][]
Maybe more if invariant is distinguished. However we remain at one single sourcecode version and up to ~N binary versions. I think this is an acceptable behaviour.
Contract declaration syntax:
(*) Currently some variants come to my mind:
f(Tmin p : Tcontract) // a.)
f(Tmin p Tcontract) // b.)
f(Tcontract Tmin p) // c.)
f(Tmin..Tcontract p) // d.)
I think none of these would interfere with something already present. Is that right?
I personally would prefer a.) or b.) because a.) naturally signals the implicit convertibility with ":" but you may confuse with template list. b.) reminds to "Tail" const methods but here at parameter level [1]. Both have semantic similarty to what we do with parameter const contracts.
As well we could have a short handle of contract_type for the case when we want Tcontract=const(Tmin), a /full const contract/.
f(Tmin p : const) // for a.)
f(Tmin p const) // for b.)
f(const Tmin p) // for c.)
? // for d.)
That is pretty much what I proposed in [1].
Explicit, Forced and Implict Contracts:
(**) In my very first example passing an argument of type const(char[]) fails, because we could not directly sign the contract when we're just looking at the declaration.
a.) Nonetheless the compiler may be capable of signing the contract for the function if it can prove the function's ability to do so.
b.) We may be able to make a /request for contract/ of the function when calling it. Example:
char[] x = ...;
foo(x : const(char)); // request for contract when calling foo()
The compiler may then reject or prove&warant the offer.
c.) With this we may be able to reuse large amounts of D1 code with D2 stylish const types. The returntype issue remains, but that does not apply to all functions.
d.) The compiler could try to establish the contract even if neither the function offers one nor the call. Maybe this also could be of help for the optimizer.
Return type:
(***) The return before the parameter means, that the const level of the type of parameter p is applied to the return value. Right now I'm not sure what to do if we wanted to return e.g. an element of p.
One idea would be to return "auto", meaning it returns the type of the value that is actually returned by the returning return statement ;) This goes consistent with the meaning of auto. E.g.
auto foo(char[][] x : const(char[][])) {
return x[17];
}
unittest {
char[][] a = ...;
const(char)[][] b = ...;
const(char[])[] c = ...;
auto ya = foo(a);
static assert (is(typeof(ya)==char[]));
auto yb = foo(b);
static assert (is(typeof(yb)==const(char)[]));
auto yc = foo(c);
static assert (is(typeof(yc)==const(char[])));
}
But you may run into trouble elsewhere like "return found?it:other;", maybe. Another attempt:
char foo(return(char[])[] p : const(char[][])) { ... }
or
typeof(p[0]) foo(char[] p : const(char[])) {
return p[42];
}
...
const(char)[] x = ...;
auto y = foo(x);
static assert (is(typeof(y)==const(char)));
[1] http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=68137
http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=68248
[2] http://s3.amazonaws.com/dconf2007/WalterAndrei.pdf p.38/39
| |||
March 25, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Oliver Dathe | On 25/03/2008, Oliver Dathe <o.dathe@gmx.de> wrote:
> The following is some kind of superset of what I have proposed in [1]
>
> Example for syntax (*):
>
> char[] f(char[] p : const(char)[]) { }
I don't think I'd like to have to specify the type twice for every affected parameter.
I don't have a better idea though! :-(
(But I'm still thinking about it)
| |||
March 25, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Janice Caron | Janice Caron wrote:
>> char[] f(char[] p : const(char)[]) { }
>
> I don't think I'd like to have to specify the type twice for every
> affected parameter.
I'm pretty sure, that most of the time you could stick with some shorthand version like
char[] f(char[] p const) { } // or
char[] f(char[] p : const) { }
However, it seems like the leading problem is to bring the const level of parameters to the return type in an appropriate, expressive way. I think that is solved in 90% of the time with
char[] f(return char[] p const) { }
Not yet sure if that is satisfying enough.
| |||
March 26, 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. > > For me, the question is is solving these issues a large enough problem that justifies adding a rather confusing new variation on const? If I understand correctly this could be achieved with the plain old 'final' keyword. Am I right? Ciao -- Roberto Mariottini, http://www.mariottini.net/roberto/ SuperbCalc, a free tape calculator: http://www.mariottini.net/roberto/superbcalc/ | |||
March 26, 2008 Re: Proposal for scoped const contracts | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Roberto Mariottini | On 26/03/2008, Roberto Mariottini <rmariottini@mail.com> wrote:
> If I understand correctly this could be achieved with the plain old
> 'final' keyword. Am I right?
It could equally be achieved with the "heffalump" keyword, if we defined one, and made it do exactly we want. :-)
However, if you're asking whether or not there is an /existing/ way of achieving the desired goal, there isn't. So no, I think you are not right.
| |||
Copyright © 1999-2021 by the D Language Foundation
Permalink
Reply