www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - covariant final interface functions

reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
I just ran into this idea when porting dcollections to D2.

I have the following interface:

interface Addable(V)
{
   Addable add(V v);
   Addable add(V v, out bool wasAdded);
}

In all cases, I have implemented add(V) as:

MyContainerType add(V v)
{
    bool ignored;
    return add(v, ignored);
}

I would like to make add(V) a final function in the interface, except for  
one problem -- covariance.  The reason I return 'this' when adding is so  
you can chain together many operations.  Because of covariance, this works  
awesome because you always get back the type you are using.

But if I make add(V v) a final interface function, then MyContainerType  
cannot override it, and things like this won't work:

myContainerType.add(5).myContainerTypeSpecialFunction();

Plus, even if you *could* override it (a la Andrei's overridable interface  
methods), the implementation would be identical, so that's a lot of  
mindless coding.

It would be nice to be able to flag a final interface function (or any  
function for that matter) to indicate that it returns 'this', meaning it  
should be covariant automatically without repeating the implementation.  I  
don't know if there's a way to do this so the compiler doesn't have to  
actually generate another function to implement it in the derived class,  
but even that would be OK.  Even helping with the mindless recoding of the  
same simple function would be great.  It can also be another point of  
documentation (I *know* this function returns the owner object because the  
signature says so).

A proposed syntax:

final return this add(V v)
{
   bool ignored;
   add(v, ignored);
   // no return necessary?
}

There's probably not a huge need for it, but I just thought I'd put the  
idea out there.

-Steve
Mar 18 2010
next sibling parent Leandro Lucarella <llucax gmail.com> writes:
Steven Schveighoffer, el 18 de marzo a las 21:34 me escribiste:
 A proposed syntax:
 
 final return this add(V v)
 {
   bool ignored;
   add(v, ignored);
   // no return necessary?
 }
 
 There's probably not a huge need for it, but I just thought I'd put
 the idea out there.
Or you can add a proper chain operator. OOC does that (I know they borrow it from another place, I just don't remember from where): me := RandomGuy new() me eatBreakfast() .drinkCoffee() .yawn() .goBackToBed() Some smart people call that "message chaining". It's different from cascade calling, though, because eatBreakfast() and his friends don't return anything. (in OOC a space " " is like a dot "." in D, and the dot is the chaining operator) I think this is a cleaner solution for that problem, you don't have to annotate the methods with "return this" and when you read the code using it, you know all the methods are being called on the *same* object, if you do x.add(y).add(z), the second add could be called in an object other than x. -- Leandro Lucarella (AKA luca) http://llucax.com.ar/ ---------------------------------------------------------------------- GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145 104C 949E BFB6 5F5A 8D05) ----------------------------------------------------------------------
Mar 19 2010
prev sibling parent reply Justin Johansson <no spam.com> writes:
Maybe I don't understand your problem exactly, but in answer to
what you said here ...

 Because of covariance, this works  
 awesome because you always get back the type you are using.
.. If you know in the first place (presumably by static analysis) the type you are using then you know the type you expect to be returned, so why do you need to even use an interface and expect some magic covariance to be of use to you. Like I say, perhaps I misunderstand your point but feel from my own endeavours that covariant return types are rarely useful and only serve to push more entries into a virtual function table further down an inheritance tree for no purposeful gain in the end. Perhaps you could elaborate on what the real problem is that you want to solve. Please accept my apologies if your question/proposal is obvious to all and sundry; just that personally I cannot reconcile this in my own mind that there is an end to be met here by your means. Of course I always welcome enlightenment. :-) Cheers Justin Johansson Steven Schveighoffer Wrote:
 I just ran into this idea when porting dcollections to D2.
 
 I have the following interface:
 
 interface Addable(V)
 {
    Addable add(V v);
    Addable add(V v, out bool wasAdded);
 }
 
 In all cases, I have implemented add(V) as:
 
 MyContainerType add(V v)
 {
     bool ignored;
     return add(v, ignored);
 }
 
 I would like to make add(V) a final function in the interface, except for  
 one problem -- covariance.  The reason I return 'this' when adding is so  
 you can chain together many operations.  Because of covariance, this works  
 awesome because you always get back the type you are using.
 
 But if I make add(V v) a final interface function, then MyContainerType  
 cannot override it, and things like this won't work:
 
 myContainerType.add(5).myContainerTypeSpecialFunction();
 
 Plus, even if you *could* override it (a la Andrei's overridable interface  
 methods), the implementation would be identical, so that's a lot of  
 mindless coding.
 
 It would be nice to be able to flag a final interface function (or any  
 function for that matter) to indicate that it returns 'this', meaning it  
 should be covariant automatically without repeating the implementation.  I  
 don't know if there's a way to do this so the compiler doesn't have to  
 actually generate another function to implement it in the derived class,  
 but even that would be OK.  Even helping with the mindless recoding of the  
 same simple function would be great.  It can also be another point of  
 documentation (I *know* this function returns the owner object because the  
 signature says so).
 
 A proposed syntax:
 
 final return this add(V v)
 {
    bool ignored;
    add(v, ignored);
    // no return necessary?
 }
 
 There's probably not a huge need for it, but I just thought I'd put the  
 idea out there.
 
 -Steve
Mar 19 2010
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 19 Mar 2010 09:28:21 -0400, Justin Johansson <no spam.com> wrote:

 Maybe I don't understand your problem exactly, but in answer to
 what you said here ...

 Because of covariance, this works
 awesome because you always get back the type you are using.
.. If you know in the first place (presumably by static analysis) the type you are using then you know the type you expect to be returned, so why do you need to even use an interface and expect some magic covariance to be of use to you.
Because people may want to use the interface vs the actual class. There is no point of an interface if people don't use it to abstract the implementation. I want the interface to call the same function as the class, but the return type should be covariant. That is, someone who is using the derived type and wants to chain calls on such functions should not be forced to use only the interface supported calls after they make the first one.
 Like I say, perhaps I misunderstand your point but feel from my own
 endeavours that covariant return types are rarely useful and only serve
 to push more entries into a virtual function table further down an  
 inheritance
 tree for no purposeful gain in the end.
covariant functions occupy one slot of the vtable. You may be misunderstanding how covariance works. See below.
 Perhaps you could elaborate on what the real problem is that you want to  
 solve.

 Please accept my apologies if your question/proposal is obvious to all  
 and sundry;
 just that personally I cannot reconcile this in my own mind that there  
 is an end
 to be met here by your means.
No apologies needed :) I'll explain what I mean: Covariance works like this: class C { C doSomething() {...; return this;} } class D : C { D doSomething() {...; return this;} void doSomethingElse() {...} } If I have a D, I can call d.doSomething().doSomethingElse(), whereas if I have a C reference to a D, I cannot call the doSomethingElse part, but both c.doSomething() and d.doSomething() invoke the exact same function (D's version). If you look at D's virtual table, it only has 2 entries, because the covariant version overrides the base version. In C++ this is not possible, because it does not consider that you can return a pointer to something that is both a C and a D at the same time. The benefit is one implementation for both interfaces (C or D). However, if you have simple "wrapper" functions, like the ones I've outlined, in order to achieve covariance on those wrappers, you must override them. e.g.: class C { C doSomething(ref int x) {...; return this;} C doSomething() {int dummy; return doSomething(dummy);} } class D : C { D doSomething(ref int x) {...; return this;} D doSomething() {int dummy; return doSomething(dummy);} // need this reimplementation to achieve covariance } That second function of D is a complete duplicate of code, and poses a maintenance nightmare. If this is a large hierarchy, any changes I make to the base wrapper must be duplicated in each derived class. Not only that, but all versions of the function are exact copies! There is no difference in the generated code whatsoever. With the advent of final functions in interfaces, I can achieve wrapper functions in an interface (I'm using that now for some things in dcollections). However, I can't make them covariant. So I'm forced to write the wrapper functions in the implementation instead of the interface, where it belongs. It would be nice in both the interface and the class hierarchy cases to simply identify that a function is the same, but permanently covariant because it returns a pointer to this. Covariance always is possible if you return this. That's all I'm saying. -Steve
Mar 19 2010
parent reply div0 <div0 users.sourceforge.net> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Steven Schveighoffer wrote:
 In C++ this is not possible, because it does not consider that you can return
a pointer to
 something that is both a C and a D at the same time.
FYI, not true. C++ does support covariant returns. Even back as far as visual studio 2003. - -- My enormous talent is exceeded only by my outrageous laziness. http://www.ssTk.co.uk -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.7 (MingW32) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/ iD8DBQFLo8nbT9LetA9XoXwRAgE7AJ9oFQXQxxHcQHD56oH9MFictO9XCACdFhQ2 JzFbIj5z2qS3d2Ah5g+6H0Q= =emr3 -----END PGP SIGNATURE-----
Mar 19 2010
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Fri, 19 Mar 2010 15:00:43 -0400, div0 <div0 users.sourceforge.net>  
wrote:

 -----BEGIN PGP SIGNED MESSAGE-----
 Hash: SHA1

 Steven Schveighoffer wrote:
 In C++ this is not possible, because it does not consider that you can  
 return a pointer to
 something that is both a C and a D at the same time.
FYI, not true. C++ does support covariant returns. Even back as far as visual studio 2003.
I stand corrected. I just never knew about it ;) http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29 -Steve
Mar 19 2010