Jump to page: 1 2
Thread overview
structs, classes, interfaces - Part III, Solution
Sep 01, 2007
Martin Hinsch
Sep 01, 2007
Daniel Keep
Sep 02, 2007
Reiner Pope
Sep 04, 2007
Reiner Pope
Sep 01, 2007
Martin Hinsch
Sep 01, 2007
Sean Kelly
Sep 01, 2007
Martin Hinsch
Sep 01, 2007
Sean Kelly
Sep 01, 2007
BCS
September 01, 2007
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
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

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
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
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
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
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
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
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
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
« First   ‹ Prev
1 2