equivariant functions
Steven Schveighoffer
schveiguy at yahoo.com
Tue Oct 14 13:31:15 PDT 2008
"Andrei Alexandrescu" wrote
> Steven Schveighoffer wrote:
>> What about returning a member? i.e.:
>>
>> inout(typeof(s.ptr)) getptr(inout const(char)[] s) { return s.ptr;}
>
> Yah there is a recurring problem - we need to find a notation that works
> nicely and expressively for member functions as well as free functions.
>
> For free functions an easy-to-explain-and-understand way is to have
> "inout" mark the incoming / outgoing TYPE entirely, not only its
> qualifier. Then:
>
> inout stripl(inout const(char)[] s);
>
> means: accept any subtype of const(char)[] and call that type "inout" in
> the return type. Then it's easy to access dependent stuff:
>
> typeof(inout.ptr) at(inout const(char)[] s);
>
> Notice that for one-parameter functions there's not even a need to specify
> the inout in the argument list because it's unambiguous:
>
> inout stripl(const(char)[] s);
> typeof(inout.ptr) at(const(char)[] s);
>
> When you get into member functions things aren't quite nice:
>
> class A
> {
> private int a;
> inout clone() inout; // ehm
> typeof(&inout.a) getPtrToA() inout; // ehm
> }
>
>> is that what you had in mind?
>>
>> this syntax is going to be used often, since it's what you would use for
>> an accessor. So it should be simple to understand if possible.
>
> Yah I agree. At this point I don't have any solid solution for notation...
> please continue rolling out ideas.
Damn, I think there is some confusion here, because we are trying to solve
two related, but unequal problems.
First is const equivariance (is that the right term?). I want to specify a
function that treats an argument as const, but does not affect the const
contract that the caller has with that argument (i.e. my original scoped
const proposal).
The second is type equivariance. I want to specify that I will treat an
argument as a base type, but I will not change the derived type that the
caller is using.
These are similar, if you consider that const is a 'base type' of its
mutable or invariant version. But there is one caveat that is hard to
explain using this terminology. const is a type modifier, not a type. So
it's not really a base type of a type, and it can be applied to any type in
existance. Not the same as specifying a base type.
So the first requirement (const equivariance) does not require that I return
a derivative of the argument, it requires that I return an argument that is
part of the input, but contains the same const contract as the passed in
variable at the *call site*. It does not require that the return type is
derived from the input.
It is this first requirement that is the only requirement necessary for
functions which return unrelated types from their arguments. i.e., for an
accessor, which is not returning something of the same type as its argument,
the only thing that is important to keep the same at the call site is the
const modifiers. For example, you can return a base type that is
automatically converted to a derived type, but that must be accomplished
with an overridden function. That is already handled using covariance, and
I really don't think there is a way to enforce this in the compiler.
An example, a linked list.
class Link
{
private Link next;
Link getNext() { return next;}
}
class DerivedLink : Link
{
int someExtraData;
}
We currently solve this by adding a covariant getNext() to DerivedLink.
But would it be enough to just change the getNext function in the base type
to:
inout getNext() { return next; } inout;
I don't think it can be enforced. Because some other Link function can set
next to another type of Link (possibly a base Link). It is up to the
designer of DerivedLink to ensure that next always points to a descendent of
DerivedLink. In this case, I think it's a requirement to use covariant
functions.
However, it *would* be advantageous to declare getNext as not altering the
const contract of the caller. That is, it returns the same constancy that
it is called with (in this example, inout implies const):
inout(Link) getNext() { return next; } inout;
For the second requirement, I can see value in things like call-chaining:
class C
{
inout doSomething() inout { return this;}
}
class D : C
{
inout doSomethingElse() inout { return this;}
}
auto d = new D;
d.doSomething().doSomethingElse();
However, the return value MUST be exactly the same value as the input. If
it's only the same type, we lose all compiler enforcibility.
So does it make sense to split these two requirements?
I propose to use inout only to deal with const contracts, and use another
syntax to signify which parameter will be returned:
inout(X) foo(inout(Y) y); // X and Y are unrelated types
a stab at 'returning this exact parameter'
X foo(return X x); // The return value from foo will be x, so the compiler
is free to upcast automatically.
inout(X) foo(return inout(X) x); // combination, signifies that I will
return x, and I promise not to modify it in the function.
With this scheme, I think possibly inout might not be as clear...
-Steve
More information about the Digitalmars-d
mailing list