digitalmars.D - covariant final interface functions
- Steven Schveighoffer (42/42) Mar 18 2010 I just ran into this idea when porting dcollections to D2.
- Leandro Lucarella (20/31) Mar 19 2010 Or you can add a proper chain operator. OOC does that (I know they borro...
- Justin Johansson (18/75) Mar 19 2010 .. If you know in the first place (presumably by static analysis) the ty...
- Steven Schveighoffer (61/82) Mar 19 2010 Because people may want to use the interface vs the actual class. There...
- div0 (15/17) Mar 19 2010 -----BEGIN PGP SIGNED MESSAGE-----
- Steven Schveighoffer (5/13) Mar 19 2010 I stand corrected. I just never knew about it ;)
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
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
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
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 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.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.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
-----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
On Fri, 19 Mar 2010 15:00:43 -0400, div0 <div0 users.sourceforge.net> wrote:-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Steven Schveighoffer wrote:I stand corrected. I just never knew about it ;) http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29 -SteveIn 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.
Mar 19 2010