www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - contravariant argument types: wanna?

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
next sibling parent reply Marianne Gagnon <auria.mg gmail.com> writes:
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
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
prev sibling next sibling parent reply Justin Johansson <procode adam-dott-com.au> writes:
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,
 
 Andrei
Yes 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
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Justin Johansson wrote:
 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,

 Andrei
Yes 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
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. Andrei
Sep 22 2009
prev sibling next sibling parent reply dsimcha <dsimcha yahoo.com> writes:
== Quote from Andrei Alexandrescu (SeeWebsiteForEmail erdani.org)'s article
 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
If 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
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
dsimcha wrote:
 == Quote from Andrei Alexandrescu (SeeWebsiteForEmail erdani.org)'s article
 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
If 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.
There 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? Andrei
Sep 22 2009
prev sibling next sibling parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
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,
 
 Andrei
I 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
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 22 Sep 2009 20:49:59 -0400, Jeremie Pelletier <jeremiep gmail.com>  
wrote:

 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,
  Andrei
I 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"); } }
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... -Steve
Sep 22 2009
parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
Steven Schveighoffer wrote:
 On Tue, 22 Sep 2009 20:49:59 -0400, Jeremie Pelletier 
 <jeremiep gmail.com> wrote:
 
 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,
  Andrei
I 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"); } }
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... -Steve
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.
Sep 22 2009
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 22 Sep 2009 21:25:44 -0400, Jeremie Pelletier <jeremiep gmail.com>  
wrote:

 Steven Schveighoffer wrote:
 On Tue, 22 Sep 2009 20:49:59 -0400, Jeremie Pelletier  
 <jeremiep gmail.com> wrote:

 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,
  Andrei
I 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"); } }
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... -Steve
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.
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. -Steve
Sep 22 2009
parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
Steven Schveighoffer wrote:
 On Tue, 22 Sep 2009 21:25:44 -0400, Jeremie Pelletier 
 <jeremiep gmail.com> wrote:
 
 Steven Schveighoffer wrote:
 On Tue, 22 Sep 2009 20:49:59 -0400, Jeremie Pelletier 
 <jeremiep gmail.com> wrote:

 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,
  Andrei
I 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"); } }
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... -Steve
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.
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. -Steve
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 :) Jeremie
Sep 22 2009
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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 :)

 Jeremie
There 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
parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
Steven Schveighoffer wrote:
 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 :)

 Jeremie
There 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
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. Jeremie
Sep 24 2009
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 24 Sep 2009 10:54:06 -0400, Jeremie Pelletier <jeremiep gmail.com>  
wrote:

 Steven Schveighoffer wrote:
 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 :)

 Jeremie
There 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
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.
OK, my assumption was wrong, win32 is a toolkit identifier, not a platform identifier :)
 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.
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
next sibling parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
Steven Schveighoffer wrote:
 On Thu, 24 Sep 2009 10:54:06 -0400, Jeremie Pelletier 
 <jeremiep gmail.com> wrote:
 
 Steven Schveighoffer wrote:
 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 :)

 Jeremie
There 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
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.
OK, my assumption was wrong, win32 is a toolkit identifier, not a platform identifier :)
 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.
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.
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.
 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.
interface ISeekableInputStream : IInputStream, ISeekableStream {}
 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
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 24 Sep 2009 11:53:13 -0400, Jeremie Pelletier <jeremiep gmail.com>  
wrote:

 Steven Schveighoffer wrote:
  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 {}
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).
 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
prev sibling parent reply grauzone <none example.net> writes:
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
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 24 Sep 2009 12:47:12 -0400, grauzone <none example.net> wrote:

 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?
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. -Steve
Sep 24 2009
prev sibling next sibling parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
Jeremie Pelletier wrote:
 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,

 Andrei
I 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
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.
Sep 22 2009
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling next sibling parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Jeremie Pelletier wrote:
 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,

 Andrei
I 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).
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.
 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
This 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
prev sibling parent Ary Borenszweig <ary esperanto.org.ar> writes:
Jeremie Pelletier escribió:
 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,

 Andrei
I 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).
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. :-)
Sep 23 2009
prev sibling next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
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
parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
bearophile wrote:
 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
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 :) Jeremie
Sep 22 2009
parent Justin Johansson <procode adam-dott-com.au> writes:
Jeremie Pelletier Wrote:

 bearophile wrote:
 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
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 :) Jeremie
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 Johansson
Sep 22 2009
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
next sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 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*.
Good point. Then I guess contravariance for overriding might be good for completeness' sake.
 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
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
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
prev sibling next sibling parent Justin Johansson <procode adam-dott-com.au> writes:
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
prev sibling next sibling parent Rainer Deyke <rainerd eldwood.com> writes:
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
prev sibling next sibling parent reply Yigal Chripun <yigal100 gmail.com> writes:
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,

 Andrei
consider: 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
parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
Yigal Chripun wrote:
 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,

 Andrei
consider: 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?
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.
Sep 23 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Jeremie Pelletier wrote:
 Yigal Chripun wrote:
 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,

 Andrei
consider: 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?
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.
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.) Andrei
Sep 23 2009
next sibling parent reply Jeremie Pelletier <jeremiep gmail.com> writes:
Andrei Alexandrescu wrote:
 Jeremie Pelletier wrote:
 Yigal Chripun wrote:
 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,

 Andrei
consider: 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?
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.
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.
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. Jeremie
Sep 23 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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
parent reply Lutger <lutger.blijdestijn gmail.com> writes:
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
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Lutger wrote:
 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.
Well yah but there's only this many emperors in boxers that one can stand seeing together :o). Andrei
Sep 23 2009
parent "Robert Jacques" <sandford jhu.edu> writes:
On Wed, 23 Sep 2009 16:09:15 -0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:
 Lutger wrote:
 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.
Well yah but there's only this many emperors in boxers that one can stand seeing together :o). Andrei
"All that is necessary for the triumph of evil is that good men do nothing" (often misattributed to Edmund Burke)
Sep 23 2009
prev sibling parent Edward Diener <eddielee_no_spam_here tropicsoft.com> writes:
Lutger wrote:
 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.
My sensibilities are deeply offended by your first sentence.
Sep 23 2009
prev sibling next sibling parent reply Ary Borenszweig <ary esperanto.org.ar> writes:
Andrei Alexandrescu wrote:
 Jeremie Pelletier wrote:
 Yigal Chripun wrote:
 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,

 Andrei
consider: 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?
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.
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.)
So it should be: class JamesBond : Driver { override void drive(Object c) { ... } } because JamesBond can even drive a HotGirl!
Sep 23 2009
next sibling parent Jeremie Pelletier <jeremiep gmail.com> writes:
Ary Borenszweig wrote:
 Andrei Alexandrescu wrote:
 Jeremie Pelletier wrote:
 Yigal Chripun wrote:
 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,

 Andrei
consider: 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?
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.
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.)
So it should be: class JamesBond : Driver { override void drive(Object c) { ... } } because JamesBond can even drive a HotGirl!
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!
Sep 23 2009
prev sibling parent Justin Johansson <procode adam-dott-com.au> writes:
Ary Borenszweig Wrote:
 Andrei Alexandrescu wrote:
 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!
Yes, but if such occasion arises, it's a call for contraception not contravariance ;-)
Sep 23 2009
prev sibling next sibling parent Yigal Chripun <yigal100 gmail.com> writes:
On 23/09/2009 18:13, Andrei Alexandrescu wrote:
 Jeremie Pelletier wrote:
 Yigal Chripun wrote:
 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,

 Andrei
consider: 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?
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.
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.) Andrei
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?
Sep 23 2009
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Wed, 23 Sep 2009 11:13:26 -0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Jeremie Pelletier wrote:
 Yigal Chripun wrote:
 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,

 Andrei
consider: 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?
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.
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.)
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... -Steve
Sep 24 2009
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Steven Schveighoffer wrote:
 On Wed, 23 Sep 2009 11:13:26 -0400, Andrei Alexandrescu 
 <SeeWebsiteForEmail erdani.org> wrote:
 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...
It should override both. It is already the case that one method overrides several others (e.g. with multiple inheritance of interfaces). Andrei
Sep 24 2009
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Thu, 24 Sep 2009 09:06:41 -0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 Steven Schveighoffer wrote:
 On Wed, 23 Sep 2009 11:13:26 -0400, Andrei Alexandrescu  
 <SeeWebsiteForEmail erdani.org> wrote:
 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...
It should override both. It is already the case that one method overrides several others (e.g. with multiple inheritance of interfaces).
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. -Steve
Sep 24 2009
prev sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
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
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
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 
 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.
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. Andrei
Sep 23 2009
next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
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 
 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.
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).
That is a good point.
 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
parent reply BCS <none anon.com> writes:
Hello Walter,

 Andrei Alexandrescu wrote:
 
 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.
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? }
Sep 27 2009
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
BCS wrote:
 Hello Walter,
 
 Andrei Alexandrescu wrote:

 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.
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? }
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. Andrei
Sep 27 2009
prev sibling parent reply =?ISO-8859-1?Q?=22J=E9r=F4me_M=2E_Berger=22?= <jeberger free.fr> writes:
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 =
 before getting to work.
The stumbling block to contravariant parameters (not arguments) is how=
=20
 it 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=
=20
 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);
 }
=20
This 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
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
Jérôme M. Berger wrote:
 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 
 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.
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 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.
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. Andrei
Sep 23 2009
prev sibling parent Michel Fortin <michel.fortin michelf.com> writes:
On 2009-09-23 05:33:34 -0400, Walter Bright <newshound1 digitalmars.com> said:

 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.
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/
Sep 23 2009