Thread overview | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
September 01, 2007 structs, classes, interfaces - Part III, Solution | ||||
---|---|---|---|---|
| ||||
The basic idea behind my solution is actually really simple. You just have to realize that (OOP-) inheritance as it is usually done is a combination of two different concepts - composition and polymorphism. What would happen if we disentangled these concepts?
My suggestion would be to completely remove virtual functions from the language. Instead use interfaces to provide ad hoc runtime polymorphism.
As before interfaces define a set of methods a class has to provide. As before you can let a class derive from an interface to declare (and let the compiler check) its conformance to the interface. But now this is not obligatory anymore.
There are now two fundamentally different types of pointers in the language:
a) *non-polymorphic* pointers to plain data/classes
b) *polymorphic* pointers to interfaces
At any point a pointer of type a) can be cast to a pointer of type b) *at which
point the conformance to the interface is tested*. This is easily done at
compile time since in the non-polymorphic world all types are known.
I'll give an example:
---
class A
{
void foo(int a);
}
class B
{
void foo(int b);
}
interface Foo
{
void foo(int c);
}
A a;
B b;
Foo * f1 = &a, f2 = &b;
f1.foo(42); // calls a.foo
---
You could even introduce a way to construct an interface from a given class so that existing class hierarchies could remain completely unchanged:
---
interface(B) * i = &b;
---
Internally a pointer to an interface would actually be a struct containing the vtable and a pointer to the object which would be a tiny bit more costly than a class reference.
Since polymorphism has now its own separate language structure composition can be made much more powerful (similar ideas have recently been discussed for structs):
---
class A {...}
class B {...}
class C : A,B {...}
// syntactic sugar for
class C
{
A super_0;
B super_1;
use super_0;
use super_1;
}
---
I think this solves all problems I mentioned above except one, which is slicing.
To be honest I never saw the particular danger in that one in the first place.
Maybe I'm totally missing something but isn't that just another case of a lossy
cast? In that case my proposal solves that problem as well since all potentially
lossy (slicy ;-) casts can be detected at compile time and the user
warned.
I have no illusions concerning the probability of this making it into D. Still I think it's a nice idea ;-) and I would be really interested in hearing what people think about it.
--
Martin Hinsch
m.hinsch@rug.nl
+31 50 363 8097
CEES Centre for Ecological and Evolutionary Studies
Biologisch Centrum
Kerklaan 30
Postbus 14
9750 AA Haren
|
September 01, 2007 Re: structs, classes, interfaces - Part III, Solution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Martin Hinsch | Martin Hinsch wrote: > The basic idea behind my solution is actually really simple. You just have to realize that (OOP-) inheritance as it is usually done is a combination of two different concepts - composition and polymorphism. What would happen if we disentangled these concepts? > > My suggestion would be to completely remove virtual functions from the language. Instead use interfaces to provide ad hoc runtime polymorphism. > > As before interfaces define a set of methods a class has to provide. As before you can let a class derive from an interface to declare (and let the compiler check) its conformance to the interface. But now this is not obligatory anymore. > I think this solves all problems I mentioned above except one, which is slicing. To be honest I never saw the particular danger in that one in the first place. Maybe I'm totally missing something but isn't that just another case of a lossy cast? In that case my proposal solves that problem as well since all potentially lossy (slicy ;-) casts can be detected at compile time and the user warned. > > I have no illusions concerning the probability of this making it into D. Still I think it's a nice idea ;-) and I would be really interested in hearing what people think about it. I'm not sure if I understood you correctly, but isn't this exactly the thing that was discussed recently. From language consistency POV > class A {...} > class B {...} > > class C : A,B {...} is a bit problematic because D doesn't have multiple inheritance. A trait class construct could be possible though, but I cannot say if the added complexity to the core language is good. However, the interface compliance could be enforced that way (and Walter seems to like it too, see slides). Implementation could be mixed in with an extended mixin syntax: interface IA { void foo(); } interface IB { int bar(); } struct A : IA { void foo() { writefln("hello"); } struct B : IB { int bar() { return 42; } } struct C : IA, IB { mixin A; mixin B; } I've noticed mixin is a powerful mechanism. MI is more or less a combination of interface part and mixed in implementation and provides a lot less flexibility. |
September 01, 2007 Re: structs, classes, interfaces - Part III, Solution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jari-Matti Mäkelä |
Jari-Matti Mäkelä wrote:
> ...
>
> interface IA { void foo(); }
> interface IB { int bar(); }
>
> struct A : IA { void foo() { writefln("hello"); }
> struct B : IB { int bar() { return 42; } }
>
> struct C : IA, IB {
> mixin A;
> mixin B;
> }
I believe this will be possible using:
struct C : IA, IB {
private A a;
private B b;
alias a this;
alias b this;
}
-- Daniel
|
September 01, 2007 Re: structs, classes, interfaces - Part III, Solution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Martin Hinsch | Martin Hinsch wrote:
>
> I have no illusions concerning the probability of this making it into D.
> Still I think it's a nice idea ;-) and I would be really interested in
> hearing what people think about it.
To sidestep the issue for a moment, do templates solve your problem for arrays of structs, or is there some reason you need run-time polymorphism?
Sean
|
September 01, 2007 Re: structs, classes, interfaces - Part III, Solution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jari-Matti Mäkelä | On Sat, 01 Sep 2007 16:20:44 +0300, Jari-Matti Mäkelä wrote: > Martin Hinsch wrote: > >> The basic idea behind my solution is actually really simple. You just have to realize that (OOP-) inheritance as it is usually done is a combination of two different concepts - composition and polymorphism. What would happen if we disentangled these concepts? >> >> My suggestion would be to completely remove virtual functions from the language. Instead use interfaces to provide ad hoc runtime polymorphism. >> >> As before interfaces define a set of methods a class has to provide. As before you can let a class derive from an interface to declare (and let the compiler check) its conformance to the interface. But now this is not obligatory anymore. > >> I think this solves all problems I mentioned above except one, which is slicing. To be honest I never saw the particular danger in that one in the first place. Maybe I'm totally missing something but isn't that just another case of a lossy cast? In that case my proposal solves that problem as well since all potentially lossy (slicy ;-) casts can be detected at compile time and the user warned. >> >> I have no illusions concerning the probability of this making it into D. Still I think it's a nice idea ;-) and I would be really interested in hearing what people think about it. > > I'm not sure if I understood you correctly, but isn't this exactly the thing that was discussed recently. From language consistency POV > >> class A {...} >> class B {...} >> >> class C : A,B {...} > > is a bit problematic because D doesn't have multiple inheritance. A trait class construct could be possible though, but I cannot say if the added complexity to the core language is good. However, the interface compliance could be enforced that way (and Walter seems to like it too, see slides). Implementation could be mixed in with an extended mixin syntax: > > interface IA { void foo(); } > interface IB { int bar(); } > > struct A : IA { void foo() { writefln("hello"); } struct B : IB { int > bar() { return 42; } } > > struct C : IA, IB { > mixin A; > mixin B; > } > } > I've noticed mixin is a powerful mechanism. MI is more or less a > combination of interface part and mixed in implementation and provides a > lot less flexibility. Yes, as I said, the "enhanced composition" part has been discussed recently. However only for structs and only in a very specific way. Anyways the point of my proposal is not that but rather that run-time polymorphism is completely separated from (and thus made orthogonal to) constructing classes from other classes (aka derivation). In the *second step* this takes a big burden from the class hierarchy and makes it possible to be much more expressive with glueing various bits and pieces together to create a new class. The great thing is that as soon as derivation *only* does composition you can treat it as a special case of a general class of patterns which involve taking pieces of data + functions and stick them together like lego blocks to build something new. This kind of pattern is actually quite common nowadays in C++, I believe. You take a bunch of light-weight classes and combine them as data representation, algorithm, etc. parts of a bigger class. You add run-time polymorphism by adding in an ABC which declares the required methods. My idea would make this last step much more elegant and simple. -- Martin Hinsch m.hinsch@rug.nl +31 50 363 8097 CEES Centre for Ecological and Evolutionary Studies Biologisch Centrum Kerklaan 30 Postbus 14 9750 AA Haren |
September 01, 2007 Re: structs, classes, interfaces - Part III, Solution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Sean Kelly | On Sat, 01 Sep 2007 09:34:31 -0700, Sean Kelly wrote: > Martin Hinsch wrote: >> >> I have no illusions concerning the probability of this making it into D. Still I think it's a nice idea ;-) and I would be really interested in hearing what people think about it. > > To sidestep the issue for a moment, do templates solve your problem for arrays of structs, or is there some reason you need run-time polymorphism? > > > Sean I feel a bit dumb but I have no idea what you mean... The basic problem is that I want to have a proper OOP type but with all the speed and memory advantages value semantics offer PLUS the option of painlessly "upgrading" it later to something even more OOPishy (i.e. rt-polymorphism, reference semantics). -- Martin Hinsch m.hinsch@rug.nl +31 50 363 8097 CEES Centre for Ecological and Evolutionary Studies Biologisch Centrum Kerklaan 30 Postbus 14 9750 AA Haren |
September 01, 2007 Re: structs, classes, interfaces - Part III, Solution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Martin Hinsch | Martin Hinsch wrote:
> On Sat, 01 Sep 2007 09:34:31 -0700, Sean Kelly wrote:
>
>> Martin Hinsch wrote:
>>> I have no illusions concerning the probability of this making it into D.
>>> Still I think it's a nice idea ;-) and I would be really interested in
>>> hearing what people think about it.
>> To sidestep the issue for a moment, do templates solve your problem for
>> arrays of structs, or is there some reason you need run-time polymorphism?
>
> I feel a bit dumb but I have no idea what you mean... The basic problem is
> that I want to have a proper OOP type but with all the speed and memory
> advantages value semantics offer PLUS the option of painlessly "upgrading"
> it later to something even more OOPishy (i.e. rt-polymorphism, reference
> semantics).
See my other post "implicit template function overloading broken?" for an example. However, templates limit polymorphism to compile-time.
Sean
|
September 01, 2007 Re: structs, classes, interfaces - Part III, Solution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Daniel Keep | Daniel Keep wrote: > > > Jari-Matti Mäkelä wrote: >> ... >> >> interface IA { void foo(); } >> interface IB { int bar(); } >> >> struct A : IA { void foo() { writefln("hello"); } >> struct B : IB { int bar() { return 42; } } >> >> struct C : IA, IB { >> mixin A; >> mixin B; >> } > > I believe this will be possible using: > > struct C : IA, IB { > private A a; > private B b; > alias a this; > alias b this; > } > I don't doubt it isn't possible, but it needs to be implemented first too :) One problem I see in the new aliasing syntax is that it introduces two unnecessary symbols C.a and C.b as an side effect. (but it could allow you to choose between a.foo and b.foo if foo happens to conflict, though) Another way to implement this could be to allow "template inheritance". In that case only non-virtual inheritance model needs to be implemented, no new mixins or aliases: >> template A : IA { void foo() { writefln("hello"); } >> template B : IB { int bar() { return 42; } } >> >> struct C : IA, IB { >> mixin A!(); // or maybe even mixin A; when there are no arguments >> mixin B!(); >> } |
September 01, 2007 Re: structs, classes, interfaces - Part III, Solution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Martin Hinsch | Reply to Martin, > The basic idea behind my solution is actually really simple. You just > have to realize that (OOP-) inheritance as it is usually done is a combination of two different concepts - composition and polymorphism. What would happen if we disentangled these concepts? > My suggestion would be to completely remove virtual functions from the > language. Instead use interfaces to provide ad hoc runtime polymorphism. > An interesting idea. However I agree that it's not likely to make it in. However another option that can be taken from the "oop is two things" standpoint is to allow composition in structs (the forthcoming "alias foo this;" gets much of that) You can already do this (I think) in a some what hackish manner in that you can treat a pointer to a struct as a pointer to the type of it's first member. A cleaner way to do things would be to allow a struct to inherent from another struct but with the restriction that the new struct can't override anything from the base struct and that casting to the derived type is an unsafe operation. > Internally a pointer to an interface would actually be a struct > containing the vtable and a pointer to the object which would be a > tiny bit more costly than a class reference. > > I've wanted this for a LONG time. It would allow all sorts of fun things to implement an interface (functions, structs, maybe even basic types). Alas it would break COM or require a totally incompatible special case. (I darn to heck whoever defined the COM standard that way!) |
September 02, 2007 Re: structs, classes, interfaces - Part III, Solution | ||||
---|---|---|---|---|
| ||||
Posted in reply to Jari-Matti Mäkelä | Jari-Matti Mäkelä wrote:
> Daniel Keep wrote:
>
>>
>> Jari-Matti Mäkelä wrote:
>>> ...
>>>
>>> interface IA { void foo(); }
>>> interface IB { int bar(); }
>>>
>>> struct A : IA { void foo() { writefln("hello"); }
>>> struct B : IB { int bar() { return 42; } }
>>>
>>> struct C : IA, IB {
>>> mixin A;
>>> mixin B;
>>> }
>> I believe this will be possible using:
>>
>> struct C : IA, IB {
>> private A a;
>> private B b;
>> alias a this;
>> alias b this;
>> }
>>
>
> I don't doubt it isn't possible, but it needs to be implemented first too :)
> One problem I see in the new aliasing syntax is that it introduces two
> unnecessary symbols C.a and C.b as an side effect. (but it could allow you
> to choose between a.foo and b.foo if foo happens to conflict, though)
I wonder if we can extend the alias this syntax to get rid of the unnecessary symbols. Since, conceptually, aliasing just "binds" the second name to the first, giving the first variable a new name, why don't we just allow variables to be named "this". The code above becomes
struct C : IA, IB {
A this;
B this;
}
To me it's quite consistent with "alias b this".
-- Reiner
|
Copyright © 1999-2021 by the D Language Foundation