equivariant functions

Steven Schveighoffer schveiguy at yahoo.com
Mon Oct 13 13:44:49 PDT 2008


"Andrei Alexandrescu" wrote
> Many functions return one of their parameters regardless of the way it was 
> qualified.
>
> char[] stripl(char[] s);
> const(char)[] stripl(const(char)[] s);
> invariant(char)[] stripl(invariant(char)[] s);
>
> Stripl is not a particularly good example because it needs to work on 
> wchar and dchar too, but let's ignore that aspect for now.
>
> There's been several proposals in this group on tackling that problem.
>
> In unrelated proposals and discussions, people mentioned the need for 
> functions that return the exact type of this:
>
> class A { A clone(); }
> class B : A { B clone(); }
>
> How can we declare A.clone such that all of its derived classes have it 
> return their own type?
>
> It took me a while to realize they are really very related. This is easy 
> to figure out if you think that invariant(char)[] and char[] are subtypes 
> of const(char)[]!
>
> I discussed with Walter a variant that implements equivariant functions 
> without actually adding an explicit feature to the language. Consider:
>
> typeof(s) stripl(const(char)[] s);
>
> This signature states that it returns the same type as an argument. I 
> propose that that pattern means stripl can accept _any_ subtype of 
> const(char)[] and return that exact type. Inside the function, however, 
> the type of s is the type declared, thus restricting its use.
>
> I need to convince myself that function bodies of this type can be 
> reliably typechecked, but first I wanted to run it by everyone to get a 
> feel of it.
>
> Equivariant functions are not (necessarily) templates and can be used as 
> virtual functions. Only one body is generated for one equivariant 
> function, unless other template mechanisms are in vigor.
>
> Here are some examples:
>
> a) Simple equivariance
>
> typeof(s) stripl(const(char)[] s);
>
> b) Parameterized equivariance
>
> typeof(s) stripl(S)(S s) if (isSomeString!S);
>
> c) Equivariance of field:
>
> typeof(s.ptr) getpointer(const(char)[] s);
>
> d) Equivariance inside a class/struct declaration:
>
> class S
> {
>     typeof(this) clone();
>     typeof(this.field) getfield();
>     int field;
> }
>
> What do you think? I'm almost afraid to post this.

I'm glad you are looking into this.  This is along the same vein as what I 
called 'scoped const' (see 
http://d.puremagic.com/issues/show_bug.cgi?id=1961), but I only addressed 
const variance.

Just in terms of const, I have a case where your solution doesn't help/work.

Easiest case is min/max:

typeof(v1) min(T)(const(T) v1, const(T) v2) { if(v1 < v2) return v1; return 
v2;}

Now, imagine you call it like this:

char[] best = "bb".dup;
best = min(aa, "aa");

If the function and usage are allowed to compile, then this results in best, 
being a char[], to be pointing to an invariant(char)[].

There are two problems that need to be solved here.  First, you need another 
const type.  One that is treated as const, but is implicitly castable back 
to the argument type, and can't be implicitly casted to.  That type modifier 
needs to be perpetuated throughout the function, because you shouldn't be 
able to return things that didn't originate from the input.  If I create a 
temporary variable, I should have to use this modifier to declare the 
variable (in my proposal, at the suggestion of Janice, I used 'inout', a 
dead keyword).  This guarantees that your output is a subset of the input.

An example of how min looks in my proposal (with the proposed 'inout' 
keyword):

inout(T) min(T)(inout(T) v1, inout(T) v2)

The second problem is, you want the return type to be dependent on both v1 
and v2.  In my proposal, the return type could depend on multiple arguments, 
and if they varied by constancy, the return type was defaulted to const, as 
this is the only possible type that anything can be casted to.

You might be tempted to do something like this:

typeof(v1) min(T)(const(T) v1, typeof(v1) v2)

But then if you pass in the mutable version as the first, the function fails 
to compile in the usage I have above.

In terms of the auto casting to derived types (not just const), I have to 
get my head around it before I can think of possible counter cases, but 
const is definitely broken by your solution (or at least, the solution 
doesn't handle all cases).

-Steve 





More information about the Digitalmars-d mailing list