digitalmars.D - contravariant argument types: wanna?
- Andrei Alexandrescu (25/25) Sep 22 2009 Hello,
- Marianne Gagnon (5/42) Sep 22 2009 I'm admittedly not a D expert, and have not written compilers; but the f...
- Andrei Alexandrescu (12/18) Sep 22 2009 Inheritance makes all the difference. Consider:
- Justin Johansson (9/46) Sep 22 2009 Yes please. Gimme, gimme, gimme. Andrei and Walter, you guys are lege...
- Andrei Alexandrescu (6/60) Sep 22 2009 Thanks. To paraphrase Mark Twain, the reports of Walter and I being
- dsimcha (7/32) Sep 22 2009 If I understand covariance and contravariance right, this seems like a n...
- Andrei Alexandrescu (7/41) Sep 22 2009 There is no need to explicitly declare contravariance. Accepting
- Jeremie Pelletier (24/61) Sep 22 2009 I can't think of an use for contravariant parameters, since a B is
- Steven Schveighoffer (9/57) Sep 22 2009 I don't know if this is possible:
- Jeremie Pelletier (5/73) Sep 22 2009 Not everywhere, only where it detects covariant/contravariant overrides
- Steven Schveighoffer (14/82) Sep 22 2009 I don't think it's worth the trouble. Dynamic casts are not as cheap as...
- Jeremie Pelletier (54/142) Sep 22 2009 Yeah most of my display interfaces would make use of covariant
- Steven Schveighoffer (23/77) Sep 24 2009 There are some possible solutions. First, it looks like you are using
- Jeremie Pelletier (31/125) Sep 24 2009 Because there will be multiple implementations living in the same
- Steven Schveighoffer (35/152) Sep 24 2009 OK, my assumption was wrong, win32 is a toolkit identifier, not a platfo...
- Jeremie Pelletier (24/191) Sep 24 2009 Well the interface arguments are part of the interface declarations in
- Steven Schveighoffer (12/23) Sep 24 2009 You'd think this works well, but it still doesn't cut it. There are abo...
- grauzone (3/6) Sep 24 2009 So, if you want to see if an object supports seeking, you must call the
- Steven Schveighoffer (7/13) Sep 24 2009 It makes the requirement for things that want to wrap streams less
- Jeremie Pelletier (25/92) Sep 22 2009 Also about contravariance, using your original A and B objects, what
- Steven Schveighoffer (4/13) Sep 22 2009 This will not compile, An A does not implicitly cast to a B.
- Andrei Alexandrescu (7/73) Sep 22 2009 The point is that B can state it is ok with a more general type than its...
- Ary Borenszweig (16/58) Sep 23 2009 The thing is you have this:
- bearophile (5/11) Sep 22 2009 Let's fix fix the holes in the logic of the module system and other basi...
- Jeremie Pelletier (7/21) Sep 22 2009 I think the development priorities in D are also to get a final D2
- Justin Johansson (7/31) Sep 22 2009 I well understand bearophile's concern but I think Jeremie has a good po...
- Steven Schveighoffer (40/63) Sep 22 2009 http://d.puremagic.com/issues/show_bug.cgi?id=3075
- Steven Schveighoffer (25/32) Sep 22 2009 OK, here is an abstract example, maybe someone can put some real-world
- Andrei Alexandrescu (24/80) Sep 22 2009 Good point. Then I guess contravariance for overriding might be good for...
- Steven Schveighoffer (5/20) Sep 22 2009 Ah, yes, I did not think of the case of a tree vs. a line :)
- Justin Johansson (7/14) Sep 22 2009 FWIW, some background reading material which might be useful to others.
- Rainer Deyke (9/11) Sep 22 2009 I'm usually pretty good at thinking of contrived examples in support of
- Yigal Chripun (11/36) Sep 22 2009 consider:
- Jeremie Pelletier (4/57) Sep 23 2009 You just described covariant arguments, which is a feature i'd also like...
- Andrei Alexandrescu (31/89) Sep 23 2009 Well there's a good reason for it: contravariant arguments are sound,
- Jeremie Pelletier (16/81) Sep 23 2009 Have you read my other post in this thread where I show an actual
- Andrei Alexandrescu (33/48) Sep 23 2009 I did. I don't have enough information to comment on the design, but at
- Lutger (5/7) Sep 23 2009 Please, it's much more important to write a good book than to avoid poss...
- Andrei Alexandrescu (4/15) Sep 23 2009 Well yah but there's only this many emperors in boxers that one can
- Robert Jacques (4/18) Sep 23 2009 "All that is necessary for the triumph of evil is that good men do
- Edward Diener (2/11) Sep 23 2009 My sensibilities are deeply offended by your first sentence.
- Ary Borenszweig (6/103) Sep 23 2009 So it should be:
- Jeremie Pelletier (7/114) Sep 23 2009 class MultiTask {
- Justin Johansson (2/19) Sep 23 2009 Yes, but if such occasion arises, it's a call for contraception not cont...
- Yigal Chripun (8/99) Sep 23 2009 I second Andrei's point that covariance isn't sound. however, my example...
- Steven Schveighoffer (12/101) Sep 24 2009 Your example just triggered a possible problem with contravariance.
- Andrei Alexandrescu (4/44) Sep 24 2009 It should override both. It is already the case that one method
- Steven Schveighoffer (21/61) Sep 24 2009 Yes, but interfaces have no implementation -- it's clear you want to
- Walter Bright (7/11) Sep 23 2009 The stumbling block to contravariant parameters (not arguments) is how
- Andrei Alexandrescu (16/29) Sep 23 2009 I don't see that as a huge problem. First off, right now the override
- Walter Bright (4/37) Sep 23 2009 I would really want to get away from the notion of selecting which
- BCS (11/31) Sep 27 2009 a torture test:
- Andrei Alexandrescu (6/43) Sep 27 2009 This is simple - it overrides both. There are more complicated cases
- =?ISO-8859-1?Q?=22J=E9r=F4me_M=2E_Berger=22?= (12/42) Sep 23 2009 =20
- Andrei Alexandrescu (5/40) Sep 23 2009 The only reason for which I used A and B in the arguments list was to
- Michel Fortin (22/35) Sep 23 2009 Yes, that rule is simple, even simplistic. If keeping this rule simple
Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, Andrei
Sep 22 2009
I'm admittedly not a D expert, and have not written compilers; but the first thing that came to my mind is that the second fun() you posted was not an override, but overload. i.e. the same as the following, except with inheritance : print(int a) { /* ... */ } print(float a) { /* ... */ } print(string a) { /* ... */ } Now the main question, IMO, is : how will the compiler differenciate whether you want to override or overload? Would that change prevent overloading methods with different signatures?Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, Andrei
Sep 22 2009
Marianne Gagnon wrote:I'm admittedly not a D expert, and have not written compilers; but the first thing that came to my mind is that the second fun() you posted was not an override, but overload. i.e. the same as the following, except with inheritance : print(int a) { /* ... */ } print(float a) { /* ... */ } print(string a) { /* ... */ }Inheritance makes all the difference. Consider: B b = new B; A a = b; auto c1 = b.clone(); auto c2 = a.clone(); Both calls go to the same method, although c1 and c2 have different types.Now the main question, IMO, is : how will the compiler differenciate whether you want to override or overload? Would that change prevent overloading methods with different signatures?The rule is simple - for overriding to happen, you must (1) use the override keyword, (2) have the result type a subtype of the result type of the overridden method, and (3) have the parameter types supertypes of the respective parameter types of the overriden method. Andrei
Sep 22 2009
Andrei Alexandrescu Wrote:Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiYes please. Gimme, gimme, gimme. Andrei and Walter, you guys are legends. Currently I'm developing a specialized collection library based on templated quantified types. Such library is useful in pattern matching applications. Now despite any (negative) comments I may have made on prior occasion regarding Scala being too academic re covariance & contravariance, it is difficult producing this type of library without language support for covariant and contravariant typing. Designing such support into D would bring even more credibility to the language .. especially if you guys can come up with a neat syntax to effect this feature. I'm impressed with the way Walter observed that the ! operator was not a binary operator in C/C++ and then went about re-purposing it for template syntax. Accordingly I wouldn't be surprised if you guys pulled another cool rabbit-out-of-the-hat trick. So yes there is extreme interest in my neck of the woods but as for a killer example, I need to think this out in more time than this reply makes for at the moment. If you do this, I suspect that it will be the end of my D1 adventures and force me into D2. Cheers Justin Johansson
Sep 22 2009
Justin Johansson wrote:Andrei Alexandrescu Wrote:Thanks. To paraphrase Mark Twain, the reports of Walter and I being legend are greatly exaggerated. Syntactically there is no need to change anything, just carry the change in the implementation. AndreiHello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiYes please. Gimme, gimme, gimme. Andrei and Walter, you guys are legends. Currently I'm developing a specialized collection library based on templated quantified types. Such library is useful in pattern matching applications. Now despite any (negative) comments I may have made on prior occasion regarding Scala being too academic re covariance & contravariance, it is difficult producing this type of library without language support for covariant and contravariant typing. Designing such support into D would bring even more credibility to the language .. especially if you guys can come up with a neat syntax to effect this feature. I'm impressed with the way Walter observed that the ! operator was not a binary operator in C/C++ and then went about re-purposing it for template syntax. Accordingly I wouldn't be surprised if you guys pulled another cool rabbit-out-of-the-hat trick. So yes there is extreme interest in my neck of the woods but as for a killer example, I need to think this out in more time than this reply makes for at the moment. If you do this, I suspect that it will be the end of my D1 adventures and force me into D2. Cheers Justin Johansson
Sep 22 2009
== Quote from Andrei Alexandrescu (SeeWebsiteForEmail erdani.org)'s articleHello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiIf I understand covariance and contravariance right, this seems like a no-brainer and probably easy to implement if you already have a working familiarity with the DMD codebase. What would there be to the implementation besides: 1. Making the compiler accept class hierarchies for which contravariant arguments are declared, and 2. Inserting the necessary implicit upcasts.
Sep 22 2009
dsimcha wrote:== Quote from Andrei Alexandrescu (SeeWebsiteForEmail erdani.org)'s articleThere is no need to explicitly declare contravariance. Accepting contravariant parameters would simply extend the notion of overriding. Implementation-wise, I suspect some trampolines would be needed. Do references to objects need adjustment when implicitly cast to references to interfaces? AndreiHello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiIf I understand covariance and contravariance right, this seems like a no-brainer and probably easy to implement if you already have a working familiarity with the DMD codebase. What would there be to the implementation besides: 1. Making the compiler accept class hierarchies for which contravariant arguments are declared, and 2. Inserting the necessary implicit upcasts.
Sep 22 2009
Andrei Alexandrescu wrote:Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiI can't think of an use for contravariant parameters, since a B is guaranteed to always be a A, I don't see the point of being able to declare fun(A). However, I would love to hear about covariant parameters, it would be most useful for interface implementations: interface A { A fun(A); } class B : A { B fun(B); } class C : A { C fun(C); } Currently you need some pretty boring boilerplate code, which isn't complicated but gets repetitive when you have hundreds of such cases: class B : A { B fun(A) { if(B b = cast(B)b) // do stuff else throw Error("Invalid object type"); } } Jeremie
Sep 22 2009
On Tue, 22 Sep 2009 20:49:59 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Andrei Alexandrescu wrote:I don't know if this is possible: A a = new C; a.fun(new A); // oops, you just passed an A into a function which requires a C! Are you suggesting that the compiler insert dynamic cast checks everywhere? Cause that seems like a lot of overhead... -SteveHello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiI can't think of an use for contravariant parameters, since a B is guaranteed to always be a A, I don't see the point of being able to declare fun(A). However, I would love to hear about covariant parameters, it would be most useful for interface implementations: interface A { A fun(A); } class B : A { B fun(B); } class C : A { C fun(C); } Currently you need some pretty boring boilerplate code, which isn't complicated but gets repetitive when you have hundreds of such cases: class B : A { B fun(A) { if(B b = cast(B)b) // do stuff else throw Error("Invalid object type"); } }
Sep 22 2009
Steven Schveighoffer wrote:On Tue, 22 Sep 2009 20:49:59 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Not everywhere, only where it detects covariant/contravariant overrides or implementations. In these cases you would already use explicit dynamic casts so the compiler generated code would just lower the required boilerplate.Andrei Alexandrescu wrote:I don't know if this is possible: A a = new C; a.fun(new A); // oops, you just passed an A into a function which requires a C! Are you suggesting that the compiler insert dynamic cast checks everywhere? Cause that seems like a lot of overhead... -SteveHello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiI can't think of an use for contravariant parameters, since a B is guaranteed to always be a A, I don't see the point of being able to declare fun(A). However, I would love to hear about covariant parameters, it would be most useful for interface implementations: interface A { A fun(A); } class B : A { B fun(B); } class C : A { C fun(C); } Currently you need some pretty boring boilerplate code, which isn't complicated but gets repetitive when you have hundreds of such cases: class B : A { B fun(A) { if(B b = cast(B)b) // do stuff else throw Error("Invalid object type"); } }
Sep 22 2009
On Tue, 22 Sep 2009 21:25:44 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Steven Schveighoffer wrote:I don't think it's worth the trouble. Dynamic casts are not as cheap as implicit casts. Contravariance on parameters can be statically proven by the compiler. I agree Andrei's example isn't that compelling (to be fair, he did ask if anyone had a good example, indicating his wasn't), but there are other examples that are more compelling (see the bug report I referenced in a separate sub-thread). For instance, if you only ever use class C, and never instantiate an A or B instance, you still pay the dynamic cast penalty every time you call fun! It doesn't sound to me like a good design. I suppose you probably have run into this before, perhaps a real example would be more convincing. -SteveOn Tue, 22 Sep 2009 20:49:59 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Not everywhere, only where it detects covariant/contravariant overrides or implementations. In these cases you would already use explicit dynamic casts so the compiler generated code would just lower the required boilerplate.Andrei Alexandrescu wrote:I don't know if this is possible: A a = new C; a.fun(new A); // oops, you just passed an A into a function which requires a C! Are you suggesting that the compiler insert dynamic cast checks everywhere? Cause that seems like a lot of overhead... -SteveHello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiI can't think of an use for contravariant parameters, since a B is guaranteed to always be a A, I don't see the point of being able to declare fun(A). However, I would love to hear about covariant parameters, it would be most useful for interface implementations: interface A { A fun(A); } class B : A { B fun(B); } class C : A { C fun(C); } Currently you need some pretty boring boilerplate code, which isn't complicated but gets repetitive when you have hundreds of such cases: class B : A { B fun(A) { if(B b = cast(B)b) // do stuff else throw Error("Invalid object type"); } }
Sep 22 2009
Steven Schveighoffer wrote:On Tue, 22 Sep 2009 21:25:44 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Yeah most of my display interfaces would make use of covariant arguments, I use main abstract factory for the entire package, and the objects it creates contain factory methods themselves. I plan to have implementations for all of win32, gdk, x11, quartz, cairo, pango, d2d, dwrite, gl, gl3 and finally d3d7 up to d3d11. Most of the client code will therefore see only the interfaces in order to maintain portability, and to allow different implementations to live in the same executable (for example win32/gl/cairo/pango for up to vista or win32/d3d/d2d/dwrite if on win7 and up). Here is a watered down version of a few interfaces I use, which are used by client code: interface IDrawable {} interface IWindow : IDrawable {} // onscreen drawable interface ISurface : IDrawable {} // offscreen drawable interface IDisplayContext {} // base of 2d-3d contextes interface IRenderContext {} // 3d context interface IWindowRenderContext {} // specialized onscreen 3d context interface IRenderer { IWindowRenderContext CreateRenderContext(IWindow); ISurfaceRenderContext CreateRenderContext(ISurface); } And some of their current implementation, which are all used within the package: abstract class Win32Drawable : IDrawable {} final class Win32Window : Win32Drawable, IWindow {} final class Win32Surface : Win32Drawable, IWindow {} final class GLRenderer : IRenderer { GLWindowRenderContext CreateRenderContext(IWindow window) { if(auto win32Window = cast(Win32Window)window) return new GLWindowRenderContext(win32Window); else throw new Error(); } GLSurfaceRenderContext CreateRenderContext(ISurface surface) { if(auto win32Surface = cast(Win32Surface)surface) return new GLSurfaceRenderContext(win32Surface); else throw new Error(); } } abstract class GLRenderContext : IRenderContext {} final class GLWindowRenderContext : GLRenderContext, IWindowRenderContext { this(Win32Window) {} } final class GLSurfaceRenderContext : GLRenderContext, ISurfaceRenderContext { this(Win32Surface) {} } I have over a hundred of such methods doing dynamic casts across all the different implementations like these twos in this package alone, a display interface is quite a large beast. Of course if you can suggest a better way of doing methods expecting a specific implementation of an object, while still allowing client code to call them with the interface pointer, I'd be glad to implement it :) JeremieSteven Schveighoffer wrote:I don't think it's worth the trouble. Dynamic casts are not as cheap as implicit casts. Contravariance on parameters can be statically proven by the compiler. I agree Andrei's example isn't that compelling (to be fair, he did ask if anyone had a good example, indicating his wasn't), but there are other examples that are more compelling (see the bug report I referenced in a separate sub-thread). For instance, if you only ever use class C, and never instantiate an A or B instance, you still pay the dynamic cast penalty every time you call fun! It doesn't sound to me like a good design. I suppose you probably have run into this before, perhaps a real example would be more convincing. -SteveOn Tue, 22 Sep 2009 20:49:59 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Not everywhere, only where it detects covariant/contravariant overrides or implementations. In these cases you would already use explicit dynamic casts so the compiler generated code would just lower the required boilerplate.Andrei Alexandrescu wrote:I don't know if this is possible: A a = new C; a.fun(new A); // oops, you just passed an A into a function which requires a C! Are you suggesting that the compiler insert dynamic cast checks everywhere? Cause that seems like a lot of overhead... -SteveHello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiI can't think of an use for contravariant parameters, since a B is guaranteed to always be a A, I don't see the point of being able to declare fun(A). However, I would love to hear about covariant parameters, it would be most useful for interface implementations: interface A { A fun(A); } class B : A { B fun(B); } class C : A { C fun(C); } Currently you need some pretty boring boilerplate code, which isn't complicated but gets repetitive when you have hundreds of such cases: class B : A { B fun(A) { if(B b = cast(B)b) // do stuff else throw Error("Invalid object type"); } }
Sep 22 2009
On Tue, 22 Sep 2009 22:02:59 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Yeah most of my display interfaces would make use of covariant arguments, I use main abstract factory for the entire package, and the objects it creates contain factory methods themselves. I plan to have implementations for all of win32, gdk, x11, quartz, cairo, pango, d2d, dwrite, gl, gl3 and finally d3d7 up to d3d11. Most of the client code will therefore see only the interfaces in order to maintain portability, and to allow different implementations to live in the same executable (for example win32/gl/cairo/pango for up to vista or win32/d3d/d2d/dwrite if on win7 and up). Here is a watered down version of a few interfaces I use, which are used by client code: interface IDrawable {} interface IWindow : IDrawable {} // onscreen drawable interface ISurface : IDrawable {} // offscreen drawable interface IDisplayContext {} // base of 2d-3d contextes interface IRenderContext {} // 3d context interface IWindowRenderContext {} // specialized onscreen 3d context interface IRenderer { IWindowRenderContext CreateRenderContext(IWindow); ISurfaceRenderContext CreateRenderContext(ISurface); } And some of their current implementation, which are all used within the package: abstract class Win32Drawable : IDrawable {} final class Win32Window : Win32Drawable, IWindow {} final class Win32Surface : Win32Drawable, IWindow {} final class GLRenderer : IRenderer { GLWindowRenderContext CreateRenderContext(IWindow window) { if(auto win32Window = cast(Win32Window)window) return new GLWindowRenderContext(win32Window); else throw new Error(); } GLSurfaceRenderContext CreateRenderContext(ISurface surface) { if(auto win32Surface = cast(Win32Surface)surface) return new GLSurfaceRenderContext(win32Surface); else throw new Error(); } } abstract class GLRenderContext : IRenderContext {} final class GLWindowRenderContext : GLRenderContext, IWindowRenderContext { this(Win32Window) {} } final class GLSurfaceRenderContext : GLRenderContext, ISurfaceRenderContext { this(Win32Surface) {} } I have over a hundred of such methods doing dynamic casts across all the different implementations like these twos in this package alone, a display interface is quite a large beast. Of course if you can suggest a better way of doing methods expecting a specific implementation of an object, while still allowing client code to call them with the interface pointer, I'd be glad to implement it :) JeremieThere are some possible solutions. First, it looks like you are using interfaces to abstract the platform, which seems more appropriate for version statements. I once wrote an OS abstraction library in C++, and in my fanatic attempt to avoid using the preprocessor for anything, I made everything interfaces (pure abstract classes). I think with D, the version statements are a much better solution, and will reduce overhead quite a bit. Second, Your IRenderer is the one responsible for creating a render context, but it depends on being "hooked up" with the appropriate IWindow or ISurface object. However, the IRenderer implementation's methods are pretty much static (granted they might be trimmed down). Why not move them into the IWindow and ISurface interfaces? interface IWindow : IDrawable { IWindowRenderContext CreateRenderContext(); } interface ISurface : IDrawable { ISurfaceRenderContext CreateRenderContext(); } If you need an instance of IRenderer for some other reason not shown, then consider using a hidden singleton of the correct implementation. -Steve
Sep 24 2009
Steven Schveighoffer wrote:On Tue, 22 Sep 2009 22:02:59 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Because there will be multiple implementations living in the same executable. We all know how microsoft likes to force new technology to new windows versions. I want support for Direct2D, DirectWrite, and all versions of Direct3D. Which requires different implementations for 7, vista, and xp. Then some people might have better performance with GL, for graphic adapter vendor and driver issues, so I'm throwing such an implementation in the mix. On unix you have a choice of different windowing toolkits, be it Gnome, Qt, Xfce or directly using X11 but losing specific features offered by the toolkits. As for merging IRenderer with the drawables, this wouldn't fit my design. I use what I call render contexts and paint contexts for 3d and 2d drawing respectively, which are both built upon a common set of display interfaces to get most of their shared concepts unified and compatible with one another. I also have font layering and rendering interfaces usable by the two. And given that many different render and paint implementations can be used from the same drawable targets, it wouldn't make sense. My first design was using version statements but it was far from flexible enough for my needs. The overhead is mostly needed to allocate the interfaces, not to use them so the speed isn't affected. Then you get my I/O interface which roots at simple input/output streams, then seekable streams, binary streams, file streams, async streams, pipes, etc. And get different implementations for local filesystem I/O, sockets, specialized file format abstractions, and whatnot. So for example a method expecting an IInputStream does not care what is implementing it, so long as it has a read method implemented, be it reading data from a file, from a network connection, from a packed file within an archive. These implementations still need covariant parameters within themselves for a few things. JeremieYeah most of my display interfaces would make use of covariant arguments, I use main abstract factory for the entire package, and the objects it creates contain factory methods themselves. I plan to have implementations for all of win32, gdk, x11, quartz, cairo, pango, d2d, dwrite, gl, gl3 and finally d3d7 up to d3d11. Most of the client code will therefore see only the interfaces in order to maintain portability, and to allow different implementations to live in the same executable (for example win32/gl/cairo/pango for up to vista or win32/d3d/d2d/dwrite if on win7 and up). Here is a watered down version of a few interfaces I use, which are used by client code: interface IDrawable {} interface IWindow : IDrawable {} // onscreen drawable interface ISurface : IDrawable {} // offscreen drawable interface IDisplayContext {} // base of 2d-3d contextes interface IRenderContext {} // 3d context interface IWindowRenderContext {} // specialized onscreen 3d context interface IRenderer { IWindowRenderContext CreateRenderContext(IWindow); ISurfaceRenderContext CreateRenderContext(ISurface); } And some of their current implementation, which are all used within the package: abstract class Win32Drawable : IDrawable {} final class Win32Window : Win32Drawable, IWindow {} final class Win32Surface : Win32Drawable, IWindow {} final class GLRenderer : IRenderer { GLWindowRenderContext CreateRenderContext(IWindow window) { if(auto win32Window = cast(Win32Window)window) return new GLWindowRenderContext(win32Window); else throw new Error(); } GLSurfaceRenderContext CreateRenderContext(ISurface surface) { if(auto win32Surface = cast(Win32Surface)surface) return new GLSurfaceRenderContext(win32Surface); else throw new Error(); } } abstract class GLRenderContext : IRenderContext {} final class GLWindowRenderContext : GLRenderContext, IWindowRenderContext { this(Win32Window) {} } final class GLSurfaceRenderContext : GLRenderContext, ISurfaceRenderContext { this(Win32Surface) {} } I have over a hundred of such methods doing dynamic casts across all the different implementations like these twos in this package alone, a display interface is quite a large beast. Of course if you can suggest a better way of doing methods expecting a specific implementation of an object, while still allowing client code to call them with the interface pointer, I'd be glad to implement it :) JeremieThere are some possible solutions. First, it looks like you are using interfaces to abstract the platform, which seems more appropriate for version statements. I once wrote an OS abstraction library in C++, and in my fanatic attempt to avoid using the preprocessor for anything, I made everything interfaces (pure abstract classes). I think with D, the version statements are a much better solution, and will reduce overhead quite a bit. Second, Your IRenderer is the one responsible for creating a render context, but it depends on being "hooked up" with the appropriate IWindow or ISurface object. However, the IRenderer implementation's methods are pretty much static (granted they might be trimmed down). Why not move them into the IWindow and ISurface interfaces? interface IWindow : IDrawable { IWindowRenderContext CreateRenderContext(); } interface ISurface : IDrawable { ISurfaceRenderContext CreateRenderContext(); } If you need an instance of IRenderer for some other reason not shown, then consider using a hidden singleton of the correct implementation. -Steve
Sep 24 2009
On Thu, 24 Sep 2009 10:54:06 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Steven Schveighoffer wrote:OK, my assumption was wrong, win32 is a toolkit identifier, not a platform identifier :)On Tue, 22 Sep 2009 22:02:59 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Because there will be multiple implementations living in the same executable. We all know how microsoft likes to force new technology to new windows versions. I want support for Direct2D, DirectWrite, and all versions of Direct3D. Which requires different implementations for 7, vista, and xp. Then some people might have better performance with GL, for graphic adapter vendor and driver issues, so I'm throwing such an implementation in the mix. On unix you have a choice of different windowing toolkits, be it Gnome, Qt, Xfce or directly using X11 but losing specific features offered by the toolkits.Yeah most of my display interfaces would make use of covariant arguments, I use main abstract factory for the entire package, and the objects it creates contain factory methods themselves. I plan to have implementations for all of win32, gdk, x11, quartz, cairo, pango, d2d, dwrite, gl, gl3 and finally d3d7 up to d3d11. Most of the client code will therefore see only the interfaces in order to maintain portability, and to allow different implementations to live in the same executable (for example win32/gl/cairo/pango for up to vista or win32/d3d/d2d/dwrite if on win7 and up). Here is a watered down version of a few interfaces I use, which are used by client code: interface IDrawable {} interface IWindow : IDrawable {} // onscreen drawable interface ISurface : IDrawable {} // offscreen drawable interface IDisplayContext {} // base of 2d-3d contextes interface IRenderContext {} // 3d context interface IWindowRenderContext {} // specialized onscreen 3d context interface IRenderer { IWindowRenderContext CreateRenderContext(IWindow); ISurfaceRenderContext CreateRenderContext(ISurface); } And some of their current implementation, which are all used within the package: abstract class Win32Drawable : IDrawable {} final class Win32Window : Win32Drawable, IWindow {} final class Win32Surface : Win32Drawable, IWindow {} final class GLRenderer : IRenderer { GLWindowRenderContext CreateRenderContext(IWindow window) { if(auto win32Window = cast(Win32Window)window) return new GLWindowRenderContext(win32Window); else throw new Error(); } GLSurfaceRenderContext CreateRenderContext(ISurface surface) { if(auto win32Surface = cast(Win32Surface)surface) return new GLSurfaceRenderContext(win32Surface); else throw new Error(); } } abstract class GLRenderContext : IRenderContext {} final class GLWindowRenderContext : GLRenderContext, IWindowRenderContext { this(Win32Window) {} } final class GLSurfaceRenderContext : GLRenderContext, ISurfaceRenderContext { this(Win32Surface) {} } I have over a hundred of such methods doing dynamic casts across all the different implementations like these twos in this package alone, a display interface is quite a large beast. Of course if you can suggest a better way of doing methods expecting a specific implementation of an object, while still allowing client code to call them with the interface pointer, I'd be glad to implement it :) JeremieThere are some possible solutions. First, it looks like you are using interfaces to abstract the platform, which seems more appropriate for version statements. I once wrote an OS abstraction library in C++, and in my fanatic attempt to avoid using the preprocessor for anything, I made everything interfaces (pure abstract classes). I think with D, the version statements are a much better solution, and will reduce overhead quite a bit. Second, Your IRenderer is the one responsible for creating a render context, but it depends on being "hooked up" with the appropriate IWindow or ISurface object. However, the IRenderer implementation's methods are pretty much static (granted they might be trimmed down). Why not move them into the IWindow and ISurface interfaces? interface IWindow : IDrawable { IWindowRenderContext CreateRenderContext(); } interface ISurface : IDrawable { ISurfaceRenderContext CreateRenderContext(); } If you need an instance of IRenderer for some other reason not shown, then consider using a hidden singleton of the correct implementation. -SteveAs for merging IRenderer with the drawables, this wouldn't fit my design. I use what I call render contexts and paint contexts for 3d and 2d drawing respectively, which are both built upon a common set of display interfaces to get most of their shared concepts unified and compatible with one another. I also have font layering and rendering interfaces usable by the two. And given that many different render and paint implementations can be used from the same drawable targets, it wouldn't make sense.It may well be that there is no better solution. In a statically-typed system, it's sometimes the case that an object hierarchy is too complex for compile-time detectable errors. I've ran into such cases before, and for the most part, you just have to live with it, but it shouldn't be *every* method that requires dynamic casts. You may be able to cut back on a lot of your dynamic casts by finding places where you are over-using interfaces for method detection instead of just throwing errors for unimplemented functions, or allowing too many possibilities to be passed in.My first design was using version statements but it was far from flexible enough for my needs. The overhead is mostly needed to allocate the interfaces, not to use them so the speed isn't affected.Forget about the version thing, I misunderstood why you had win32 identifiers.Then you get my I/O interface which roots at simple input/output streams, then seekable streams, binary streams, file streams, async streams, pipes, etc. And get different implementations for local filesystem I/O, sockets, specialized file format abstractions, and whatnot. So for example a method expecting an IInputStream does not care what is implementing it, so long as it has a read method implemented, be it reading data from a file, from a network connection, from a packed file within an archive. These implementations still need covariant parameters within themselves for a few things.A good example, which I happen to have some experience with. The Tango lib used to define Input and Output streams independent of Seekable streams (there was a Seek interface which was applied separately to an input/output implementation class). But what ends up happening is that it was unwieldly to use streams in cases where seeking is used, because you have to dynamic-cast to the seek interface to determine if seeking is available. The solution we ended up using is that *all* streams defined the seek function, even if they didn't support seeking, and if you called it on such objects, they just throw an exception. The compromise allows (in my opinion) a lot better code in things such as filters and buffers, which may or may not be backed by seekable streams, so may or may not need to implement seeking. The code can now simply forward the calls to the underlying object. All this aside, I still don't want covariance on parameters done *automatically* by the compiler, it adds hidden cost for not-much gain, and fosters designs that could have better alternatives. In my experience, having to dynamic cast for *everything* indicates a redesign is in order. -Steve
Sep 24 2009
Steven Schveighoffer wrote:On Thu, 24 Sep 2009 10:54:06 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Well the interface arguments are part of the interface declarations in order for the method to be visible to the client, so any class implementing them must also use interface arguments even if they only accept their own implementations. Only a subset of methods require dynamic casts, mostly those binding objects of the same implementation together from the public client interfaces. Once the objects are created and bound there is barely any covariance involved.Steven Schveighoffer wrote:OK, my assumption was wrong, win32 is a toolkit identifier, not a platform identifier :)On Tue, 22 Sep 2009 22:02:59 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Because there will be multiple implementations living in the same executable. We all know how microsoft likes to force new technology to new windows versions. I want support for Direct2D, DirectWrite, and all versions of Direct3D. Which requires different implementations for 7, vista, and xp. Then some people might have better performance with GL, for graphic adapter vendor and driver issues, so I'm throwing such an implementation in the mix. On unix you have a choice of different windowing toolkits, be it Gnome, Qt, Xfce or directly using X11 but losing specific features offered by the toolkits.Yeah most of my display interfaces would make use of covariant arguments, I use main abstract factory for the entire package, and the objects it creates contain factory methods themselves. I plan to have implementations for all of win32, gdk, x11, quartz, cairo, pango, d2d, dwrite, gl, gl3 and finally d3d7 up to d3d11. Most of the client code will therefore see only the interfaces in order to maintain portability, and to allow different implementations to live in the same executable (for example win32/gl/cairo/pango for up to vista or win32/d3d/d2d/dwrite if on win7 and up). Here is a watered down version of a few interfaces I use, which are used by client code: interface IDrawable {} interface IWindow : IDrawable {} // onscreen drawable interface ISurface : IDrawable {} // offscreen drawable interface IDisplayContext {} // base of 2d-3d contextes interface IRenderContext {} // 3d context interface IWindowRenderContext {} // specialized onscreen 3d context interface IRenderer { IWindowRenderContext CreateRenderContext(IWindow); ISurfaceRenderContext CreateRenderContext(ISurface); } And some of their current implementation, which are all used within the package: abstract class Win32Drawable : IDrawable {} final class Win32Window : Win32Drawable, IWindow {} final class Win32Surface : Win32Drawable, IWindow {} final class GLRenderer : IRenderer { GLWindowRenderContext CreateRenderContext(IWindow window) { if(auto win32Window = cast(Win32Window)window) return new GLWindowRenderContext(win32Window); else throw new Error(); } GLSurfaceRenderContext CreateRenderContext(ISurface surface) { if(auto win32Surface = cast(Win32Surface)surface) return new GLSurfaceRenderContext(win32Surface); else throw new Error(); } } abstract class GLRenderContext : IRenderContext {} final class GLWindowRenderContext : GLRenderContext, IWindowRenderContext { this(Win32Window) {} } final class GLSurfaceRenderContext : GLRenderContext, ISurfaceRenderContext { this(Win32Surface) {} } I have over a hundred of such methods doing dynamic casts across all the different implementations like these twos in this package alone, a display interface is quite a large beast. Of course if you can suggest a better way of doing methods expecting a specific implementation of an object, while still allowing client code to call them with the interface pointer, I'd be glad to implement it :) JeremieThere are some possible solutions. First, it looks like you are using interfaces to abstract the platform, which seems more appropriate for version statements. I once wrote an OS abstraction library in C++, and in my fanatic attempt to avoid using the preprocessor for anything, I made everything interfaces (pure abstract classes). I think with D, the version statements are a much better solution, and will reduce overhead quite a bit. Second, Your IRenderer is the one responsible for creating a render context, but it depends on being "hooked up" with the appropriate IWindow or ISurface object. However, the IRenderer implementation's methods are pretty much static (granted they might be trimmed down). Why not move them into the IWindow and ISurface interfaces? interface IWindow : IDrawable { IWindowRenderContext CreateRenderContext(); } interface ISurface : IDrawable { ISurfaceRenderContext CreateRenderContext(); } If you need an instance of IRenderer for some other reason not shown, then consider using a hidden singleton of the correct implementation. -SteveAs for merging IRenderer with the drawables, this wouldn't fit my design. I use what I call render contexts and paint contexts for 3d and 2d drawing respectively, which are both built upon a common set of display interfaces to get most of their shared concepts unified and compatible with one another. I also have font layering and rendering interfaces usable by the two. And given that many different render and paint implementations can be used from the same drawable targets, it wouldn't make sense.It may well be that there is no better solution. In a statically-typed system, it's sometimes the case that an object hierarchy is too complex for compile-time detectable errors. I've ran into such cases before, and for the most part, you just have to live with it, but it shouldn't be *every* method that requires dynamic casts. You may be able to cut back on a lot of your dynamic casts by finding places where you are over-using interfaces for method detection instead of just throwing errors for unimplemented functions, or allowing too many possibilities to be passed in.interface ISeekableInputStream : IInputStream, ISeekableStream {}My first design was using version statements but it was far from flexible enough for my needs. The overhead is mostly needed to allocate the interfaces, not to use them so the speed isn't affected.Forget about the version thing, I misunderstood why you had win32 identifiers.Then you get my I/O interface which roots at simple input/output streams, then seekable streams, binary streams, file streams, async streams, pipes, etc. And get different implementations for local filesystem I/O, sockets, specialized file format abstractions, and whatnot. So for example a method expecting an IInputStream does not care what is implementing it, so long as it has a read method implemented, be it reading data from a file, from a network connection, from a packed file within an archive. These implementations still need covariant parameters within themselves for a few things.A good example, which I happen to have some experience with. The Tango lib used to define Input and Output streams independent of Seekable streams (there was a Seek interface which was applied separately to an input/output implementation class). But what ends up happening is that it was unwieldly to use streams in cases where seeking is used, because you have to dynamic-cast to the seek interface to determine if seeking is available.The solution we ended up using is that *all* streams defined the seek function, even if they didn't support seeking, and if you called it on such objects, they just throw an exception. The compromise allows (in my opinion) a lot better code in things such as filters and buffers, which may or may not be backed by seekable streams, so may or may not need to implement seeking. The code can now simply forward the calls to the underlying object. All this aside, I still don't want covariance on parameters done *automatically* by the compiler, it adds hidden cost for not-much gain, and fosters designs that could have better alternatives. In my experience, having to dynamic cast for *everything* indicates a redesign is in order.My I/O implementation barely uses dynamic casting since there is very little object to object communication within a single implementation, I mean, after implementing a stream interface you're pretty much done. The display package quite a few interfaces to implement for every single backend. APIs such as DirectX already are only available through COM which only works with interface pointers, it must also do dynamic cast to ensure I don't make a custom ID3D10Texture2D implementation and try to use it. Mozilla's entire platform (other than nspr) is built upon such interfaces in IDL as a bridge between the possible C++ implementations and their JavaScript bindings. Covariant arguments is something you're gonna come across at some point or another. Having language support for it does not make it the rule, but a convenience for the exception. Jeremie
Sep 24 2009
On Thu, 24 Sep 2009 11:53:13 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Steven Schveighoffer wrote:You'd think this works well, but it still doesn't cut it. There are about a dozen filter classes in tango that would have to have seekable and non-seekable versions. This was the original problem (that you couldn't seek the filter classes).The Tango lib used to define Input and Output streams independent of Seekable streams (there was a Seek interface which was applied separately to an input/output implementation class). But what ends up happening is that it was unwieldly to use streams in cases where seeking is used, because you have to dynamic-cast to the seek interface to determine if seeking is available.interface ISeekableInputStream : IInputStream, ISeekableStream {}Covariant arguments is something you're gonna come across at some point or another. Having language support for it does not make it the rule, but a convenience for the exception.Having language support makes it not an exception, even if it's not the rule. The reason it's not automatic is because the language designer wants you to know "hey, you're doing something that isn't cheap." Just silently allowing it makes for code that too easily can call multiple dynamic casts every time you call a method. -Steve
Sep 24 2009
Steven Schveighoffer wrote:The solution we ended up using is that *all* streams defined the seek function, even if they didn't support seeking, and if you called it on such objects, they just throw an exception.So, if you want to see if an object supports seeking, you must call the method + catch the exception? How is this better than a dynamic cast?
Sep 24 2009
On Thu, 24 Sep 2009 12:47:12 -0400, grauzone <none example.net> wrote:Steven Schveighoffer wrote:It makes the requirement for things that want to wrap streams less stringent. For example, I can implement a Buffer that handles both seekable and non-seekable streams instead of 2 buffer types. It also has less of a penalty for cases where you know the stream is seekable. -SteveThe solution we ended up using is that *all* streams defined the seek function, even if they didn't support seeking, and if you called it on such objects, they just throw an exception.So, if you want to see if an object supports seeking, you must call the method + catch the exception? How is this better than a dynamic cast?
Sep 24 2009
Jeremie Pelletier wrote:Andrei Alexandrescu wrote:Also about contravariance, using your original A and B objects, what would happen in the following case: void main() { A a = new B; a.fun(new A); // Error! (in theory) } Should the compiler disallow the call or assume there can be a contravariant overload? what happens if theres another subclass requiring a B argument in the a.fun(new A); call. With covariant arguments you just need to let the compiler generate the boilerplate in the method's prolog and not have to worry about other implementations, so long as they are also all covariant. For contravariant arguments you'd need special prolog code in every method in order to allow the above case. Unless you just disallow it and enforce the contravariance only when calling directly into a B or its subclasses. Writing this last paragraph made me realize contravariance could be useful to alter the required object in a subclass chain: class A { A foo(B); } // can be called using B or C only class B : A { override A foo(A); } // can be called using A, B, C or D class C : B { override A foo(C); } // can be called using C only class D : A { override A foo(D); } // can be called using D only Here you would only need special prolog code for B, C, and D to perform a dynamic cast if needed.Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiI can't think of an use for contravariant parameters, since a B is guaranteed to always be a A, I don't see the point of being able to declare fun(A). However, I would love to hear about covariant parameters, it would be most useful for interface implementations: interface A { A fun(A); } class B : A { B fun(B); } class C : A { C fun(C); } Currently you need some pretty boring boilerplate code, which isn't complicated but gets repetitive when you have hundreds of such cases: class B : A { B fun(A) { if(B b = cast(B)b) // do stuff else throw Error("Invalid object type"); } } Jeremie
Sep 22 2009
On Tue, 22 Sep 2009 21:21:10 -0400, Jeremie Pelletier <jeremiep gmail.com> wrote:Also about contravariance, using your original A and B objects, what would happen in the following case: void main() { A a = new B; a.fun(new A); // Error! (in theory) } Should the compiler disallow the call or assume there can be a contravariant overload? what happens if theres another subclass requiring a B argument in the a.fun(new A); call.This will not compile, An A does not implicitly cast to a B. -Steve
Sep 22 2009
Jeremie Pelletier wrote:Andrei Alexandrescu wrote:The point is that B can state it is ok with a more general type than its base. This may be useful when you have a B instead of an A at your disposal.Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiI can't think of an use for contravariant parameters, since a B is guaranteed to always be a A, I don't see the point of being able to declare fun(A).However, I would love to hear about covariant parameters, it would be most useful for interface implementations: interface A { A fun(A); } class B : A { B fun(B); } class C : A { C fun(C); } Currently you need some pretty boring boilerplate code, which isn't complicated but gets repetitive when you have hundreds of such cases: class B : A { B fun(A) { if(B b = cast(B)b) // do stuff else throw Error("Invalid object type"); } } JeremieThis is what Eiffel does, and as your throw shows, is unsound. Covariant parameters is often mentioned when an explanation of Eiffel's demise is sought. Andrei
Sep 22 2009
Jeremie Pelletier escribió:Andrei Alexandrescu wrote:The thing is you have this: class C : A { } C c; void foo(B someB) { someB.fun(c); } If contravariant arguments are not supported then fun can only receive a B, not a C. If you want to pass any A you can't, but maybe B.foo relaxes the requirements on it's arguments. Of course you can pass B to it, which is already an A, but you can't pass a C which is another A. That's the use case. I can't come up with a killer example for this, I know I used covariant return types in Java before but never contravariant argument types. But if it's possible to implement it, I'd go for it. It's the right thing to do. And academic people will thank you and be happy. :-)Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, AndreiI can't think of an use for contravariant parameters, since a B is guaranteed to always be a A, I don't see the point of being able to declare fun(A).
Sep 23 2009
Andrei Alexandrescu:Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example?Let's fix fix the holes in the logic of the module system and other basic things like returning static arrays, etc. Then we can think about adding contravariant argument types. Very common and simple things that work first, complex things that are useful only once in while later. Bye, bearophile
Sep 22 2009
bearophile wrote:Andrei Alexandrescu:I think the development priorities in D are also to get a final D2 specification, and then work out the issues. Its much less work in the end since you can possibly break things with every new feature so the sooner the spec is final the sooner we can get a stable D2 release. I myself want to see covariant arguments :) JeremieToday D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example?Let's fix fix the holes in the logic of the module system and other basic things like returning static arrays, etc. Then we can think about adding contravariant argument types. Very common and simple things that work first, complex things that are useful only once in while later. Bye, bearophile
Sep 22 2009
Jeremie Pelletier Wrote:bearophile wrote:I well understand bearophile's concern but I think Jeremie has a good point. It is important to try and set the scope of the D2 work asap and then forever more steer clear of feature creep. Another poster on this forum, Graham St Jack, has raised issued about the shared mechanism being up in the air. Whilst this has nothing to do with the co/contra variance issue, it is telling of grass roots instability at the requirements engineering & spec level. From my point of view I will resist all temptation to move from D1 to D2 until the language spec itself stabilizes. I don't mind if an implementation has a few (or even few more than desirable); if the language spec is a moving target then that's another issue and all the more reason to stick with the legacy/stable version. So summing up, it would be great to see the language features specified and bugs ironed out of the spec first (Jeremie's take) and then see feature creep halted and implementation bugs fixed (bearophile's desire). -- Justin JohanssonAndrei Alexandrescu:I think the development priorities in D are also to get a final D2 specification, and then work out the issues. Its much less work in the end since you can possibly break things with every new feature so the sooner the spec is final the sooner we can get a stable D2 release. I myself want to see covariant arguments :) JeremieToday D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example?Let's fix fix the holes in the logic of the module system and other basic things like returning static arrays, etc. Then we can think about adding contravariant argument types. Very common and simple things that work first, complex things that are useful only once in while later. Bye, bearophile
Sep 22 2009
On Tue, 22 Sep 2009 20:07:22 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example?http://d.puremagic.com/issues/show_bug.cgi?id=3075 I thought Walter didn't want contravariance, maybe my clue was Walter saying: "[Contravariance is] an attractive idea, but it's been considered and rejected a couple of times now." But he may just have been talking about only doing contravariance on delegates, maybe he's all for contravariance in the general case, but I didn't think so. I think the bug above is the killer example, implicit casting of delegates would be *awesome*. BTW, I don't see a huge benefit from your example. If B inherits from A, then B knows about all the types A knows about (imagining an example where the parameters were some other class hierarchy, like C and D), so does it make a lot of sense to limit the arguments to B.fun to a base class of something B must already know about? I mean, it's not like B doesn't know about the derived type, how hard would it be to just use the derived type? Maybe I'm missing something... The other part of contravariance which bearophile brought up a while back is contravariance (and covariance) of template parameters, that would also be useful, but would require some annotation. e.g.: class C(in T) // means compiler enforces that C only ever uses T as an input { void foo(T) {...} } class A {} class B: A {} void fun(C!B c) { auto b = new B; c.foo(b);} void main() { auto c = new C!A fun(c); // legal } A good example for C would be a comparator object. But I think the absolute best usage is implicit delegate casting. That should be a no-brainer in my mind. -Steve
Sep 22 2009
On Tue, 22 Sep 2009 21:15:54 -0400, Steven Schveighoffer <schveiguy yahoo.com> wrote:BTW, I don't see a huge benefit from your example. If B inherits from A, then B knows about all the types A knows about (imagining an example where the parameters were some other class hierarchy, like C and D), so does it make a lot of sense to limit the arguments to B.fun to a base class of something B must already know about? I mean, it's not like B doesn't know about the derived type, how hard would it be to just use the derived type? Maybe I'm missing something...OK, here is an abstract example, maybe someone can put some real-world use-case to it. I have a base class A which I want to override, but I also want to implement interface I (assuming I didn't write either of them). They are defined as follows: // parameter classes class X {} class Y: X {} class A { void foo(Y y) {} } interface I { void foo(X x); } With contravariance, I could do: class B : A, I { void foo(X x) {...} } Without contravariance, I don't see how it could be done... -Steve
Sep 22 2009
Steven Schveighoffer wrote:On Tue, 22 Sep 2009 20:07:22 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Good point. Then I guess contravariance for overriding might be good for completeness' sake.Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example?http://d.puremagic.com/issues/show_bug.cgi?id=3075 I thought Walter didn't want contravariance, maybe my clue was Walter saying: "[Contravariance is] an attractive idea, but it's been considered and rejected a couple of times now." But he may just have been talking about only doing contravariance on delegates, maybe he's all for contravariance in the general case, but I didn't think so. I think the bug above is the killer example, implicit casting of delegates would be *awesome*.BTW, I don't see a huge benefit from your example. If B inherits from A, then B knows about all the types A knows about (imagining an example where the parameters were some other class hierarchy, like C and D), so does it make a lot of sense to limit the arguments to B.fun to a base class of something B must already know about? I mean, it's not like B doesn't know about the derived type, how hard would it be to just use the derived type? Maybe I'm missing something...I don't think you're missing anything, or that we're missing the same thing. Contravariance is more of a philosophy thing and "the right way to go" in a type system with subtyping. Covariance and contravariance are an expression of the general principle "it's ok for an implementation to ask for less and offer more". Practically, there may be cases in which the derived class wants to make clear that it only needs a more general parameter type. Because of that, you'd be able to issue calls that you otherwise can't. Consider: class A { void fun(Y); } static if (contravariance) class B : A { override void fun(X); } else class B : A { override void fun(Y); } class X { } class Y : X { } class Z : X { } If what you have is a B and a Z, there is absolutely no way you could make the call B.fun(Z) without contravariance. Z is unrelated to Y and therefore casting it to a Y would throw. Now the only issue is giving good names for A, B, X, Y, and Z :o). Andrei
Sep 22 2009
On Tue, 22 Sep 2009 22:58:24 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Practically, there may be cases in which the derived class wants to make clear that it only needs a more general parameter type. Because of that, you'd be able to issue calls that you otherwise can't. Consider: class A { void fun(Y); } static if (contravariance) class B : A { override void fun(X); } else class B : A { override void fun(Y); } class X { } class Y : X { } class Z : X { } If what you have is a B and a Z, there is absolutely no way you could make the call B.fun(Z) without contravariance. Z is unrelated to Y and therefore casting it to a Y would throw. Now the only issue is giving good names for A, B, X, Y, and Z :o).Ah, yes, I did not think of the case of a tree vs. a line :) this is a better example than your original... -Steve
Sep 22 2009
Andrei Alexandrescu Wrote:Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example?FWIW, some background reading material which might be useful to others. http://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) http://www.icsi.berkeley.edu/~sather/Documentation/EclecticTutorial/node5.html A lot to digest in the following paper but looks like multiple dispatch might be the killer example ??? http://cs-people.bu.edu/jhallett/papers/oops07.pdf -- Justin Johansson
Sep 22 2009
Andrei Alexandrescu wrote:Is there interest in contravariant argument types? If so, do you know of a killer example?I'm usually pretty good at thinking of contrived examples in support of obscure language features, but I can't think of *any* examples using contravariant argument types. If such an example exists, I would love to see it. Implicit casting of delegates with contravariant argument types is another story, and clearly useful. -- Rainer Deyke - rainerd eldwood.com
Sep 22 2009
On 23/09/2009 03:07, Andrei Alexandrescu wrote:Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, Andreiconsider: class Car { ... } class Truck : Car { ... } class Driver { void drive (Car c); } class truckDriver : Driver { void drive(Truck t); // NOT contra-variant !! } does the above design will be affected by your suggestion?
Sep 22 2009
Yigal Chripun wrote:On 23/09/2009 03:07, Andrei Alexandrescu wrote:You just described covariant arguments, which is a feature i'd also like to see. It's different from contravariant arguments, implementing one does not give the other unfortunately.Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, Andreiconsider: class Car { ... } class Truck : Car { ... } class Driver { void drive (Car c); } class truckDriver : Driver { void drive(Truck t); // NOT contra-variant !! } does the above design will be affected by your suggestion?
Sep 23 2009
Jeremie Pelletier wrote:Yigal Chripun wrote:Well there's a good reason for it: contravariant arguments are sound, covariant arguments aren't. My belief is that a design that would need a lot of argument covariance ought to be analyzed. I thought more about the car/truck example and I think it can be worked out nicely. The problem right now is that Truck inherits Car. But a truck is not substitutable for a car in all instances, because for example a driver able to drive a car cannot necessarily drive a truck. Here's a design that fixes that: class AutoVehicle { ... } class Car : AutoVehicle { ... } class Truck : AutoVehicle { ... } class Driver { // A driver is licensed to drive a car void drive(Car c); } class TruckDriver : Driver { // A truck driver is licensed to drive a car... override void drive(Car c); // ... and a truck void drive(Truck c); // No contravariance needed yet } class JamesBond : Driver { // James Bond can drive any auto vehicle // Contravariance needed here override void drive(AutoVehicle c) { ... } } Now if what you have is a JamesBond and a Truck, you need contravariance to have him drive it. (A HotGirl may or may not be present in the scene.) AndreiOn 23/09/2009 03:07, Andrei Alexandrescu wrote:You just described covariant arguments, which is a feature i'd also like to see. It's different from contravariant arguments, implementing one does not give the other unfortunately.Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, Andreiconsider: class Car { ... } class Truck : Car { ... } class Driver { void drive (Car c); } class truckDriver : Driver { void drive(Truck t); // NOT contra-variant !! } does the above design will be affected by your suggestion?
Sep 23 2009
Andrei Alexandrescu wrote:Jeremie Pelletier wrote:Have you read my other post in this thread where I show an actual example of covariant arguments from my display interface, my I/O interfaces also use covariant arguments although they're more friendly to generic interface parameters. The point is, you see covariance all the time in interface programming when implementations expect an interface argument to be an object from that same implementation. As soon as you use an abstract factory to let the world use some implementation without being aware of which one it is, you're bound to see covariance at some point. Most people just code it explicitly, the mozilla code is *full* of explicit covariant interface requests. While they may not be sound, its still a scenario you're gonna come across sooner or later, and a scenario which involves lots of similar boilerplate that can easily be generated by the compiler. JeremieYigal Chripun wrote:Well there's a good reason for it: contravariant arguments are sound, covariant arguments aren't. My belief is that a design that would need a lot of argument covariance ought to be analyzed.On 23/09/2009 03:07, Andrei Alexandrescu wrote:You just described covariant arguments, which is a feature i'd also like to see. It's different from contravariant arguments, implementing one does not give the other unfortunately.Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, Andreiconsider: class Car { ... } class Truck : Car { ... } class Driver { void drive (Car c); } class truckDriver : Driver { void drive(Truck t); // NOT contra-variant !! } does the above design will be affected by your suggestion?
Sep 23 2009
Jeremie Pelletier wrote:Have you read my other post in this thread where I show an actual example of covariant arguments from my display interface, my I/O interfaces also use covariant arguments although they're more friendly to generic interface parameters.I did. I don't have enough information to comment on the design, but at the end of the day, statically-typed OO designs should be for families that have many commonalities and few differences. As such, access to the interface should be everything needed for carrying out most tasks. Maintaining two parallel hierarchies in which each of them must cast around from the least capable to the concrete one is, I think, a design due for some improvement. A derived class does not "extend" its base class. It specializes it, because the base class represents an archetype for a variety of types, including the derived class but much larger. So a Mammal doesn't quite "extend" Animal because Animal includes all animals, mammals and reptiles and whatever. If anything, Mammal narrows the inheritance cone from under Animal. Indeed Mammal does add state, but Animal should not be considered holding its state; it's holding its own state plus the unrealized state of any class inheriting from it. Unfortunately many designers today are blocked in the "inheritance extends" view of things. They define a base class or interface first with a narrow set of primitives, and then they "extend" it by defining new methods, which works exactly against object orientation. Every new method that's not in the parent class is an invitation for casting and against reuse and genericity. Ironically, Java put that characterization straight in the language, furthering the legitimacy of bad designs. A good OO design defines abstract yet _capable_ generic interfaces that suffice for achieving complex goals, and then implement them in concrete classes. Very rarely if ever should a piece of code know the exact implementation of an interface. I am considering discussing all of the above in detail in TDPL, but I am afraid that some refugees from other languages will be shocked.The point is, you see covariance all the time in interface programming when implementations expect an interface argument to be an object from that same implementation. As soon as you use an abstract factory to let the world use some implementation without being aware of which one it is, you're bound to see covariance at some point. Most people just code it explicitly, the mozilla code is *full* of explicit covariant interface requests. While they may not be sound, its still a scenario you're gonna come across sooner or later, and a scenario which involves lots of similar boilerplate that can easily be generated by the compiler.The "capability cast" scenario could occur now and then, but if it's too frequent it reflect a problem in the design, not in the language implementing it. Andrei
Sep 23 2009
Andrei Alexandrescu wrote: ...I am considering discussing all of the above in detail in TDPL, but I am afraid that some refugees from other languages will be shocked.Please, it's much more important to write a good book than to avoid possibly offending the sensibilities of potential readers. Besides, anymore willing to take a risk with D will surely be open to learning something new.
Sep 23 2009
Lutger wrote:Andrei Alexandrescu wrote: ...Well yah but there's only this many emperors in boxers that one can stand seeing together :o). AndreiI am considering discussing all of the above in detail in TDPL, but I am afraid that some refugees from other languages will be shocked.Please, it's much more important to write a good book than to avoid possibly offending the sensibilities of potential readers. Besides, anymore willing to take a risk with D will surely be open to learning something new.
Sep 23 2009
On Wed, 23 Sep 2009 16:09:15 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Lutger wrote:"All that is necessary for the triumph of evil is that good men do nothing" (often misattributed to Edmund Burke)Andrei Alexandrescu wrote: ...Well yah but there's only this many emperors in boxers that one can stand seeing together :o). AndreiI am considering discussing all of the above in detail in TDPL, but I am afraid that some refugees from other languages will be shocked.Please, it's much more important to write a good book than to avoid possibly offending the sensibilities of potential readers. Besides, anymore willing to take a risk with D will surely be open to learning something new.
Sep 23 2009
Lutger wrote:Andrei Alexandrescu wrote: ...My sensibilities are deeply offended by your first sentence.I am considering discussing all of the above in detail in TDPL, but I am afraid that some refugees from other languages will be shocked.Please, it's much more important to write a good book than to avoid possibly offending the sensibilities of potential readers. Besides, anymore willing to take a risk with D will surely be open to learning something new.
Sep 23 2009
Andrei Alexandrescu wrote:Jeremie Pelletier wrote:So it should be: class JamesBond : Driver { override void drive(Object c) { ... } } because JamesBond can even drive a HotGirl!Yigal Chripun wrote:Well there's a good reason for it: contravariant arguments are sound, covariant arguments aren't. My belief is that a design that would need a lot of argument covariance ought to be analyzed. I thought more about the car/truck example and I think it can be worked out nicely. The problem right now is that Truck inherits Car. But a truck is not substitutable for a car in all instances, because for example a driver able to drive a car cannot necessarily drive a truck. Here's a design that fixes that: class AutoVehicle { ... } class Car : AutoVehicle { ... } class Truck : AutoVehicle { ... } class Driver { // A driver is licensed to drive a car void drive(Car c); } class TruckDriver : Driver { // A truck driver is licensed to drive a car... override void drive(Car c); // ... and a truck void drive(Truck c); // No contravariance needed yet } class JamesBond : Driver { // James Bond can drive any auto vehicle // Contravariance needed here override void drive(AutoVehicle c) { ... } } Now if what you have is a JamesBond and a Truck, you need contravariance to have him drive it. (A HotGirl may or may not be present in the scene.)On 23/09/2009 03:07, Andrei Alexandrescu wrote:You just described covariant arguments, which is a feature i'd also like to see. It's different from contravariant arguments, implementing one does not give the other unfortunately.Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, Andreiconsider: class Car { ... } class Truck : Car { ... } class Driver { void drive (Car c); } class truckDriver : Driver { void drive(Truck t); // NOT contra-variant !! } does the above design will be affected by your suggestion?
Sep 23 2009
Ary Borenszweig wrote:Andrei Alexandrescu wrote:class MultiTask { this(Object[] tasks) {this.tasks = tasks;} Object[] tasks; } (new JamesBond).drive(new MultiTask([new Truck, new HotGirl])); He can do both at once!Jeremie Pelletier wrote:So it should be: class JamesBond : Driver { override void drive(Object c) { ... } } because JamesBond can even drive a HotGirl!Yigal Chripun wrote:Well there's a good reason for it: contravariant arguments are sound, covariant arguments aren't. My belief is that a design that would need a lot of argument covariance ought to be analyzed. I thought more about the car/truck example and I think it can be worked out nicely. The problem right now is that Truck inherits Car. But a truck is not substitutable for a car in all instances, because for example a driver able to drive a car cannot necessarily drive a truck. Here's a design that fixes that: class AutoVehicle { ... } class Car : AutoVehicle { ... } class Truck : AutoVehicle { ... } class Driver { // A driver is licensed to drive a car void drive(Car c); } class TruckDriver : Driver { // A truck driver is licensed to drive a car... override void drive(Car c); // ... and a truck void drive(Truck c); // No contravariance needed yet } class JamesBond : Driver { // James Bond can drive any auto vehicle // Contravariance needed here override void drive(AutoVehicle c) { ... } } Now if what you have is a JamesBond and a Truck, you need contravariance to have him drive it. (A HotGirl may or may not be present in the scene.)On 23/09/2009 03:07, Andrei Alexandrescu wrote:You just described covariant arguments, which is a feature i'd also like to see. It's different from contravariant arguments, implementing one does not give the other unfortunately.Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, Andreiconsider: class Car { ... } class Truck : Car { ... } class Driver { void drive (Car c); } class truckDriver : Driver { void drive(Truck t); // NOT contra-variant !! } does the above design will be affected by your suggestion?
Sep 23 2009
Ary Borenszweig Wrote:Andrei Alexandrescu wrote:Yes, but if such occasion arises, it's a call for contraception not contravariance ;-)class JamesBond : Driver { // James Bond can drive any auto vehicle // Contravariance needed here override void drive(AutoVehicle c) { ... } } Now if what you have is a JamesBond and a Truck, you need contravariance to have him drive it. (A HotGirl may or may not be present in the scene.)So it should be: class JamesBond : Driver { override void drive(Object c) { ... } } because JamesBond can even drive a HotGirl!
Sep 23 2009
On 23/09/2009 18:13, Andrei Alexandrescu wrote:Jeremie Pelletier wrote:I second Andrei's point that covariance isn't sound. however, my example should still be valid. If you require the override keyword to enable contra-variance that would be OK, I think. in my example the drive method is overloaded since we want truckDriver to drive Cars and Trucks. is there an example where you'd want co-variance with overriding instead of overloading?Yigal Chripun wrote:Well there's a good reason for it: contravariant arguments are sound, covariant arguments aren't. My belief is that a design that would need a lot of argument covariance ought to be analyzed. I thought more about the car/truck example and I think it can be worked out nicely. The problem right now is that Truck inherits Car. But a truck is not substitutable for a car in all instances, because for example a driver able to drive a car cannot necessarily drive a truck. Here's a design that fixes that: class AutoVehicle { ... } class Car : AutoVehicle { ... } class Truck : AutoVehicle { ... } class Driver { // A driver is licensed to drive a car void drive(Car c); } class TruckDriver : Driver { // A truck driver is licensed to drive a car... override void drive(Car c); // ... and a truck void drive(Truck c); // No contravariance needed yet } class JamesBond : Driver { // James Bond can drive any auto vehicle // Contravariance needed here override void drive(AutoVehicle c) { ... } } Now if what you have is a JamesBond and a Truck, you need contravariance to have him drive it. (A HotGirl may or may not be present in the scene.) AndreiOn 23/09/2009 03:07, Andrei Alexandrescu wrote:You just described covariant arguments, which is a feature i'd also like to see. It's different from contravariant arguments, implementing one does not give the other unfortunately.Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, Andreiconsider: class Car { ... } class Truck : Car { ... } class Driver { void drive (Car c); } class truckDriver : Driver { void drive(Truck t); // NOT contra-variant !! } does the above design will be affected by your suggestion?
Sep 23 2009
On Wed, 23 Sep 2009 11:13:26 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Jeremie Pelletier wrote:Your example just triggered a possible problem with contravariance. Consider this class: class Bad : TruckDriver { override void drive(AutoVehicle c) { ...} } What does this override, drive(Truck) or drive(Car), or both? What if you didn't want to override both? My instinct is that this should be an error, to keep things simple. But it might be very annoying for some designs... -SteveYigal Chripun wrote:Well there's a good reason for it: contravariant arguments are sound, covariant arguments aren't. My belief is that a design that would need a lot of argument covariance ought to be analyzed. I thought more about the car/truck example and I think it can be worked out nicely. The problem right now is that Truck inherits Car. But a truck is not substitutable for a car in all instances, because for example a driver able to drive a car cannot necessarily drive a truck. Here's a design that fixes that: class AutoVehicle { ... } class Car : AutoVehicle { ... } class Truck : AutoVehicle { ... } class Driver { // A driver is licensed to drive a car void drive(Car c); } class TruckDriver : Driver { // A truck driver is licensed to drive a car... override void drive(Car c); // ... and a truck void drive(Truck c); // No contravariance needed yet } class JamesBond : Driver { // James Bond can drive any auto vehicle // Contravariance needed here override void drive(AutoVehicle c) { ... } } Now if what you have is a JamesBond and a Truck, you need contravariance to have him drive it. (A HotGirl may or may not be present in the scene.)On 23/09/2009 03:07, Andrei Alexandrescu wrote:You just described covariant arguments, which is a feature i'd also like to see. It's different from contravariant arguments, implementing one does not give the other unfortunately.Hello, Today, overriding functions have covariant return types: class A { A clone(); } class B : A { B clone(); // fine, overrides A.clone } That is entirely principled and cool. Now the entire story is that overriding function may have not only covariant return types, but also contravariant argument types: class A { A fun(B); } class B : A { B fun(A); // fine (in theory), overrides A.fun } Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work. Is there interest in contravariant argument types? If so, do you know of a killer example? Thanks, Andreiconsider: class Car { ... } class Truck : Car { ... } class Driver { void drive (Car c); } class truckDriver : Driver { void drive(Truck t); // NOT contra-variant !! } does the above design will be affected by your suggestion?
Sep 24 2009
Steven Schveighoffer wrote:On Wed, 23 Sep 2009 11:13:26 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:It should override both. It is already the case that one method overrides several others (e.g. with multiple inheritance of interfaces). Andreiclass AutoVehicle { ... } class Car : AutoVehicle { ... } class Truck : AutoVehicle { ... } class Driver { // A driver is licensed to drive a car void drive(Car c); } class TruckDriver : Driver { // A truck driver is licensed to drive a car... override void drive(Car c); // ... and a truck void drive(Truck c); // No contravariance needed yet } class JamesBond : Driver { // James Bond can drive any auto vehicle // Contravariance needed here override void drive(AutoVehicle c) { ... } } Now if what you have is a JamesBond and a Truck, you need contravariance to have him drive it. (A HotGirl may or may not be present in the scene.)Your example just triggered a possible problem with contravariance. Consider this class: class Bad : TruckDriver { override void drive(AutoVehicle c) { ...} } What does this override, drive(Truck) or drive(Car), or both? What if you didn't want to override both? My instinct is that this should be an error, to keep things simple. But it might be very annoying for some designs...
Sep 24 2009
On Thu, 24 Sep 2009 09:06:41 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:Steven Schveighoffer wrote:Yes, but interfaces have no implementation -- it's clear you want to override both because there's no alternative. Here is another example: class RaceCar : Car {...} class RaceCarDriver : Driver { alias Driver.drive drive; void drive(RaceCar c) {...} // doesn't override Driver.drive(Car), because he drives normal cars in a different way } class BadDriver : RaceCarDriver // drives all cars like a race car { override void drive(Car c) {...} } Should this override RaceCarDriver.drive(RaceCar)? Currently, this is valid code (tested on 1.046) and results in different behavior than what you propose. Although it's likely in my example you *want* to override both, but there could be cases where you do not. -SteveOn Wed, 23 Sep 2009 11:13:26 -0400, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:It should override both. It is already the case that one method overrides several others (e.g. with multiple inheritance of interfaces).class AutoVehicle { ... } class Car : AutoVehicle { ... } class Truck : AutoVehicle { ... } class Driver { // A driver is licensed to drive a car void drive(Car c); } class TruckDriver : Driver { // A truck driver is licensed to drive a car... override void drive(Car c); // ... and a truck void drive(Truck c); // No contravariance needed yet } class JamesBond : Driver { // James Bond can drive any auto vehicle // Contravariance needed here override void drive(AutoVehicle c) { ... } } Now if what you have is a JamesBond and a Truck, you need contravariance to have him drive it. (A HotGirl may or may not be present in the scene.)Your example just triggered a possible problem with contravariance. Consider this class: class Bad : TruckDriver { override void drive(AutoVehicle c) { ...} } What does this override, drive(Truck) or drive(Car), or both? What if you didn't want to override both? My instinct is that this should be an error, to keep things simple. But it might be very annoying for some designs...
Sep 24 2009
Andrei Alexandrescu wrote:Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work.The stumbling block to contravariant parameters (not arguments) is how it throws a monkey wrench into the overloading rules. The problem is deciding if a function overrides or overloads, when its parameter types are different. The current rule is simple: if the parameter types are identical, it overrides. If they are not identical, it overloads.
Sep 23 2009
Walter Bright wrote:Andrei Alexandrescu wrote:I don't see that as a huge problem. First off, right now the override keyword is required (and for good reason, I might add). Second, the new rule is simple: if the overriding function can be called with the overriden function's arguments, it is overriding it. True, things get more complicated when the base class also defines a corresponding overload: class A { void fun(A); void fun(B); } class B : A { override void fun(A); } This must be either an error, or the case that B.fun overrides both overloads of fun in A. AndreiToday D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work.The stumbling block to contravariant parameters (not arguments) is how it throws a monkey wrench into the overloading rules. The problem is deciding if a function overrides or overloads, when its parameter types are different. The current rule is simple: if the parameter types are identical, it overrides. If they are not identical, it overloads.
Sep 23 2009
Andrei Alexandrescu wrote:Walter Bright wrote:That is a good point.Andrei Alexandrescu wrote:I don't see that as a huge problem. First off, right now the override keyword is required (and for good reason, I might add).Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work.The stumbling block to contravariant parameters (not arguments) is how it throws a monkey wrench into the overloading rules. The problem is deciding if a function overrides or overloads, when its parameter types are different. The current rule is simple: if the parameter types are identical, it overrides. If they are not identical, it overloads.Second, the new rule is simple: if the overriding function can be called with the overriden function's arguments, it is overriding it. True, things get more complicated when the base class also defines a corresponding overload: class A { void fun(A); void fun(B); } class B : A { override void fun(A); } This must be either an error, or the case that B.fun overrides both overloads of fun in A.I would really want to get away from the notion of selecting which function is overridden based on being a "better" match.
Sep 23 2009
Hello Walter,Andrei Alexandrescu wrote:a torture test: class B { void Fn (D,B) {} void Fn (B,D) {} } class D : B { void Fn(B,B) {} // override D,B or B,D? }Second, the new rule is simple: if the overriding function can be called with the overriden function's arguments, it is overriding it. True, things get more complicated when the base class also defines a corresponding overload: class A { void fun(A); void fun(B); } class B : A { override void fun(A); } This must be either an error, or the case that B.fun overrides both overloads of fun in A.I would really want to get away from the notion of selecting which function is overridden based on being a "better" match.
Sep 27 2009
BCS wrote:Hello Walter,This is simple - it overrides both. There are more complicated cases with longer inheritance chains and overloads defined in the derived classes. I think it's ok to hold off contravariance for now. AndreiAndrei Alexandrescu wrote:a torture test: class B { void Fn (D,B) {} void Fn (B,D) {} } class D : B { void Fn(B,B) {} // override D,B or B,D? }Second, the new rule is simple: if the overriding function can be called with the overriden function's arguments, it is overriding it. True, things get more complicated when the base class also defines a corresponding overload: class A { void fun(A); void fun(B); } class B : A { override void fun(A); } This must be either an error, or the case that B.fun overrides both overloads of fun in A.I would really want to get away from the notion of selecting which function is overridden based on being a "better" match.
Sep 27 2009
Andrei Alexandrescu wrote:Walter Bright wrote:Andrei Alexandrescu wrote:Today D does not support contravariant arguments, but Walter told me =once he'd be quite willing to implement them. It is definitely the=20 right thing to do, but Walter would want to see a compelling example ==20before getting to work.The stumbling block to contravariant parameters (not arguments) is how==20it throws a monkey wrench into the overloading rules. The problem is=20 deciding if a function overrides or overloads, when its parameter=20 types are different. The current rule is simple: if the parameter types are identical, it=20 overrides. If they are not identical, it overloads.=20 I don't see that as a huge problem. First off, right now the override=20 keyword is required (and for good reason, I might add). Second, the new=rule is simple: if the overriding function can be called with the=20 overriden function's arguments, it is overriding it. True, things get=20 more complicated when the base class also defines a corresponding overl=oad:=20 class A { void fun(A); void fun(B); } =20 class B : A { override void fun(A); } =20This is poor design since it involves crossed dependencies. The=20 ancestor should never be aware of the derived classes. Therefore, I=20 vote for it to be an error. Jerome --=20 mailto:jeberger free.fr http://jeberger.free.fr Jabber: jeberger jabber.fr
Sep 23 2009
Jérôme M. Berger wrote:Andrei Alexandrescu wrote:The only reason for which I used A and B in the arguments list was to reduce the number of types defined. You may want to replace the two in the arguments list with X and Y. AndreiWalter Bright wrote:This is poor design since it involves crossed dependencies. The ancestor should never be aware of the derived classes. Therefore, I vote for it to be an error.Andrei Alexandrescu wrote:I don't see that as a huge problem. First off, right now the override keyword is required (and for good reason, I might add). Second, the new rule is simple: if the overriding function can be called with the overriden function's arguments, it is overriding it. True, things get more complicated when the base class also defines a corresponding overload: class A { void fun(A); void fun(B); } class B : A { override void fun(A); }Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work.The stumbling block to contravariant parameters (not arguments) is how it throws a monkey wrench into the overloading rules. The problem is deciding if a function overrides or overloads, when its parameter types are different. The current rule is simple: if the parameter types are identical, it overrides. If they are not identical, it overloads.
Sep 23 2009
On 2009-09-23 05:33:34 -0400, Walter Bright <newshound1 digitalmars.com> said:Andrei Alexandrescu wrote:Yes, that rule is simple, even simplistic. If keeping this rule simple forces us to write boilerplate code where it would otherwise not be necessary, then it justs shifts complexity to the one who writes the code. Case in point: class Serializer { abstract byte[] serialize(SerializableObject o); } class UniversalSerializer : Serializer { byte[] serialize(Object o) { ... } // boilerplate code to implement Serializer.serialize override byte[] serialize(SerializableObject o) { return serialize(cast(Object)o); } } Unfortunately, people might expect being able to mix classes and interfaces with this, which isn't easily reaslizable. But I believe mixing objects and interfaces is a problem that should be solved in more general way. -- Michel Fortin michel.fortin michelf.com http://michelf.com/Today D does not support contravariant arguments, but Walter told me once he'd be quite willing to implement them. It is definitely the right thing to do, but Walter would want to see a compelling example before getting to work.The stumbling block to contravariant parameters (not arguments) is how it throws a monkey wrench into the overloading rules. The problem is deciding if a function overrides or overloads, when its parameter types are different. The current rule is simple: if the parameter types are identical, it overrides. If they are not identical, it overloads.
Sep 23 2009