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