equivariant functions

Steven Schveighoffer schveiguy at yahoo.com
Tue Oct 14 20:02:03 PDT 2008


"Andrei Alexandrescu" wrote
> Steven Schveighoffer wrote:
>> "Benji Smith" wrote
>>> Steven Schveighoffer wrote:
>>>> class A : IClonable
>>>> {
>>>>   typeof(this) clone() const { return new A();}
>>>> }
>>>>
>>>> class B : A
>>>> {
>>>> }
>>>>
>>>> B b = new B;
>>>> B x = b.clone();
>>>>
>>>> Oops, b.clone() really only returns A, so this should fail.  The real 
>>>> signature in A should be:
>>> No, b.clone() definitely returns an instance of B. It's purely 
>>> coincidental (from the caller's perspective) that B's implementation is 
>>> identical to that of A.
>>
>> B is definitely not the same as A.  B could have a different 
>> implementation, I just didn't give it one.
>>
>> e.g.:
>>
>> class A : IClonable
>> {
>>   typeof(this) clone() const { return new A(); }
>>   void foo() {writefln("This is A");}
>> }
>>
>> class B : A
>> {
>>    void foo() {writefln("This is B");}
>> }
>>
>> B b = new B;
>> b.clone().foo(); // prints "This is A"
>>
>> It can't work any other way, or else the type system would be broken.
>
> Clone is different in two ways:
>
> 1. It must be implemented in *all* derived classes
>
> 2. For each of those classes, it must return the type of that class, not 
> the type of any ancestor.
>
> Statically enforcing both 1 and 2 is the ideal case. Some languages allow 
> it by allowing classes to express the actual (leaf) type of "this". D 
> doesn't. I am not sure to what extent the feature is necessary. I would 
> have been happy to capture it together with const-equivariance, but if 
> that doesn't work, then what can I do.

I'm sure it can be done, but I wasn't thinking of that when you were 
suggesting equivariance.  That sounds more like implementation rules 
(similar to if you implement an interface, you must implement all functions 
in the interface).

What I was thinking of is putting into the signature of the function what 
the compiler has guaranteed about the return type.  i.e. the return type is 
built from argument x.

The typeof(this) seems like it has a single purpose though.  Only as the 
return type of a class member function.  It makes no sense as a struct 
member function, as you can't derive from it, and likewise for a free 
function.

I think you are talking about a third feature other than the two I 
identified elsewhere.

However, this might add to the usability of the 'return exact parameter' 
syntax.  For example, for a function foo that takes a base type A, it can't 
guarantee that it can return type that was derived from A unless it returns 
the argument itself.  But if an A class has the typeof(this) as one of its 
returns for a method, then A is guaranteeing that the return type is at 
least of the most derived type (and the compiler has already statically 
guaranteed that).

So here's what I think we could do:

1. inout (or whatever keyword people like) defines how to deal with multiple 
constancies with one function.
2. typeof(this) as a return type on an interface or class method dictates 
that
    a. all derived classes must re-implement the given function
    b. the derived class must return the most derived type in that function.
3. typeof(arg) guarantees that either
    a. the function is returning that exact arg
    b. typeof(arg) is an interface or class, and the function is returning a 
result from arg.f, where arg.f returns typeof(this).

In light of this possibility, I retract the proposal for return int x in the 
parameter list, as it would be difficult to declare a temporary variable of 
that type unambiguously.

-Steve 





More information about the Digitalmars-d mailing list