Jump to page: 1 2
Thread overview
A lightweight module for class extensions in D
Dec 05, 2008
Gregor Richards
Dec 06, 2008
Robert Fraser
Dec 06, 2008
Gregor Richards
Dec 06, 2008
Robert Fraser
Dec 07, 2008
Nick Sabalausky
Dec 07, 2008
Robert Fraser
Dec 07, 2008
Christopher Wright
Dec 07, 2008
Nick Sabalausky
Dec 06, 2008
Christopher Wright
Dec 06, 2008
Nick Sabalausky
Dec 07, 2008
Janderson
Dec 07, 2008
Christian Kamm
Dec 07, 2008
Gregor Richards
December 05, 2008
I ran into a situation where I needed (essentially) the visitor pattern, but the visitor pattern sucks, so I wanted to make something like class extensions instead (that is, methods added to a class outside of the class definition).

Of course, it's not possible to do this particularly cleanly, but I made a system that works (albeit using gross string mixins). Essentially, if you have a class A, class B : A, class C : B, you could do something like this:

mixin(extensions("A", "void", "doFoo", "", ""));

mixin(extend("A", "doFoo"));
void A_doFoo(A pthis) {
    /* method for A */
}

mixin(extend("B", "doFoo"));
void B_doFoo(B pthis) {
    /* method for B */
}

Then if you call doFoo(new A()) the call will become A_doFoo(new A()), if you call doFoo(new B()) the call will become B_doFoo(new B()), if you call doFoo(new C()) the call will become B_doFoo(new C()).

If anybody has some improvements, that'd be cool. Maybe you can get rid of the dependence on string mixins ... but I don't think templates quite cut it.

 - Gregor Richards


December 06, 2008
Gregor Richards wrote:
> I ran into a situation where I needed (essentially) the visitor pattern, but the visitor pattern sucks, so I wanted to make something like class extensions instead (that is, methods added to a class outside of the class definition).
> 
> Of course, it's not possible to do this particularly cleanly, but I made a system that works (albeit using gross string mixins). Essentially, if you have a class A, class B : A, class C : B, you could do something like this:
> 
> mixin(extensions("A", "void", "doFoo", "", ""));
> 
> mixin(extend("A", "doFoo"));
> void A_doFoo(A pthis) {
>     /* method for A */
> }
> 
> mixin(extend("B", "doFoo"));
> void B_doFoo(B pthis) {
>     /* method for B */
> }
> 
> Then if you call doFoo(new A()) the call will become A_doFoo(new A()), if you call doFoo(new B()) the call will become B_doFoo(new B()), if you call doFoo(new C()) the call will become B_doFoo(new C()).
> 
> If anybody has some improvements, that'd be cool. Maybe you can get rid of the dependence on string mixins ... but I don't think templates quite cut it.
> 
>  - Gregor Richards
> 

Pretty cool stuff, but I don't see how this is at all better than the visitor pattern. It is not checked at compile-time (so if you forget to implement one part of the hierarchy, you won't find that out until runtime) and it's likely less efficient, especially for large enough hierarchies (i.e. syntax trees). Where's the happy?
December 06, 2008
Robert Fraser wrote:
> Gregor Richards wrote:
>> I ran into a situation where I needed (essentially) the visitor pattern, but the visitor pattern sucks, so I wanted to make something like class extensions instead (that is, methods added to a class outside of the class definition).
>>
>> Of course, it's not possible to do this particularly cleanly, but I made a system that works (albeit using gross string mixins). Essentially, if you have a class A, class B : A, class C : B, you could do something like this:
>>
>> mixin(extensions("A", "void", "doFoo", "", ""));
>>
>> mixin(extend("A", "doFoo"));
>> void A_doFoo(A pthis) {
>>     /* method for A */
>> }
>>
>> mixin(extend("B", "doFoo"));
>> void B_doFoo(B pthis) {
>>     /* method for B */
>> }
>>
>> Then if you call doFoo(new A()) the call will become A_doFoo(new A()), if you call doFoo(new B()) the call will become B_doFoo(new B()), if you call doFoo(new C()) the call will become B_doFoo(new C()).
>>
>> If anybody has some improvements, that'd be cool. Maybe you can get rid of the dependence on string mixins ... but I don't think templates quite cut it.
>>
>>  - Gregor Richards
>>
> 
> Pretty cool stuff, but I don't see how this is at all better than the visitor pattern. It is not checked at compile-time (so if you forget to implement one part of the hierarchy, you won't find that out until runtime) and it's likely less efficient, especially for large enough hierarchies (i.e. syntax trees). Where's the happy?

The visitor pattern requires annotating every class in the hierarchy. That's what annoys me about it. I'd like my AST nodes to just be AST nodes.

Yes, calling a function is less efficient. It can probably be improved, I'm just not sure how yet :P

Plus, of course, although the visitor pattern is the example I gave, this is a more general mechanism.

 - Gregor Richards
December 06, 2008
Gregor Richards wrote:
> Robert Fraser wrote:
>> Gregor Richards wrote:
>>> I ran into a situation where I needed (essentially) the visitor pattern, but the visitor pattern sucks, so I wanted to make something like class extensions instead (that is, methods added to a class outside of the class definition).
>>>
>>> Of course, it's not possible to do this particularly cleanly, but I made a system that works (albeit using gross string mixins). Essentially, if you have a class A, class B : A, class C : B, you could do something like this:
>>>
>>> mixin(extensions("A", "void", "doFoo", "", ""));
>>>
>>> mixin(extend("A", "doFoo"));
>>> void A_doFoo(A pthis) {
>>>     /* method for A */
>>> }
>>>
>>> mixin(extend("B", "doFoo"));
>>> void B_doFoo(B pthis) {
>>>     /* method for B */
>>> }
>>>
>>> Then if you call doFoo(new A()) the call will become A_doFoo(new A()), if you call doFoo(new B()) the call will become B_doFoo(new B()), if you call doFoo(new C()) the call will become B_doFoo(new C()).
>>>
>>> If anybody has some improvements, that'd be cool. Maybe you can get rid of the dependence on string mixins ... but I don't think templates quite cut it.
>>>
>>>  - Gregor Richards
>>>
>>
>> Pretty cool stuff, but I don't see how this is at all better than the visitor pattern. It is not checked at compile-time (so if you forget to implement one part of the hierarchy, you won't find that out until runtime) and it's likely less efficient, especially for large enough hierarchies (i.e. syntax trees). Where's the happy?
> 
> The visitor pattern requires annotating every class in the hierarchy. That's what annoys me about it. I'd like my AST nodes to just be AST nodes.
> 
> Yes, calling a function is less efficient. It can probably be improved, I'm just not sure how yet :P
> 
> Plus, of course, although the visitor pattern is the example I gave, this is a more general mechanism.
> 
>  - Gregor Richards

I agree that this has other uses, so mad props for making it.

For the visitor pattern, I wish there was a "dynamic" type like in C#4. This way, each node needs only to be annotated once for visitors, no matter what they return (with variadic arguments used for parameters, too).

Unrelated (but the main reason I use visitors), I wish D could allow could declaring a virtual function in the class definition but implement it elsewhere (a la C++). So...

module1.d:
----------
module module1;
class A { int foo(int x); }

module 2.d:
-----------
module module2;
import module1;
int A.foo(int x) { return x; }
December 06, 2008
Gregor Richards wrote:
> I ran into a situation where I needed (essentially) the visitor pattern, but the visitor pattern sucks, so I wanted to make something like class extensions instead (that is, methods added to a class outside of the class definition).

I usually use this pattern but make it more explicit, using interfaces and direct casting in client code. I'm not sure I want to use this, given that it does use string mixins.

You could define the "extension" method more like:
char[] textension(char[] funcname, TTarget, TReturn, TArgs...)()
{
   return extension (TTarget.stringof, TReturn.stringof, funcname, GetTypedArgString!(TArgs), GetUntypedArgString!(TArgs));
}

And extend:
template extend(char[] funcname, alias func)
{
   static this ()
   {
      mixin (`__ext_` ~ funcname ~ `[ParameterTupleOf!(func)[0].classinfo] = cast(void*) &func;`);
   }
}
December 06, 2008
"Gregor Richards" <Richards@codu.org> wrote in message news:ghbld5$23j7$1@digitalmars.com...
>I ran into a situation where I needed (essentially) the visitor pattern, but the visitor pattern sucks, so I wanted to make something like class extensions instead (that is, methods added to a class outside of the class definition).
>
> Of course, it's not possible to do this particularly cleanly, but I made a system that works (albeit using gross string mixins). Essentially, if you have a class A, class B : A, class C : B, you could do something like this:
>
> mixin(extensions("A", "void", "doFoo", "", ""));
>
> mixin(extend("A", "doFoo"));
> void A_doFoo(A pthis) {
>    /* method for A */
> }
>
> mixin(extend("B", "doFoo"));
> void B_doFoo(B pthis) {
>    /* method for B */
> }
>
> Then if you call doFoo(new A()) the call will become A_doFoo(new A()), if you call doFoo(new B()) the call will become B_doFoo(new B()), if you call doFoo(new C()) the call will become B_doFoo(new C()).
>
> If anybody has some improvements, that'd be cool. Maybe you can get rid of the dependence on string mixins ... but I don't think templates quite cut it.
>
> - Gregor Richards
>

Congrats for getting an extension-like feature into D through a simple lib. But I hope nobody (*cough* Walter) mistakes this as a sufficient substitute for real extension methods (*hint* *hint*) ;)


December 07, 2008
Gregor Richards wrote:
> I ran into a situation where I needed (essentially) the visitor pattern, but the visitor pattern sucks, so I wanted to make something like class extensions instead (that is, methods added to a class outside of the class definition).
> 
> Of course, it's not possible to do this particularly cleanly, but I made a system that works (albeit using gross string mixins). Essentially, if you have a class A, class B : A, class C : B, you could do something like this:
> 
> mixin(extensions("A", "void", "doFoo", "", ""));
> 
> mixin(extend("A", "doFoo"));
> void A_doFoo(A pthis) {
>     /* method for A */
> }
> 
> mixin(extend("B", "doFoo"));
> void B_doFoo(B pthis) {
>     /* method for B */
> }
> 
> Then if you call doFoo(new A()) the call will become A_doFoo(new A()), if you call doFoo(new B()) the call will become B_doFoo(new B()), if you call doFoo(new C()) the call will become B_doFoo(new C()).
> 
> If anybody has some improvements, that'd be cool. Maybe you can get rid of the dependence on string mixins ... but I don't think templates quite cut it.
> 
>  - Gregor Richards
> 

What about using a delegate, function pointer or a functor for your visitor pattern?

-Joel
December 07, 2008
"Robert Fraser" <fraserofthenight@gmail.com> wrote in message news:ghcj02$2kpi$1@digitalmars.com...
> Gregor Richards wrote:
>> Robert Fraser wrote:
>>> Gregor Richards wrote:
>>>> I ran into a situation where I needed (essentially) the visitor pattern, but the visitor pattern sucks, so I wanted to make something like class extensions instead (that is, methods added to a class outside of the class definition).
>>>>
>>>> Of course, it's not possible to do this particularly cleanly, but I made a system that works (albeit using gross string mixins). Essentially, if you have a class A, class B : A, class C : B, you could do something like this:
>>>>
>>>> mixin(extensions("A", "void", "doFoo", "", ""));
>>>>
>>>> mixin(extend("A", "doFoo"));
>>>> void A_doFoo(A pthis) {
>>>>     /* method for A */
>>>> }
>>>>
>>>> mixin(extend("B", "doFoo"));
>>>> void B_doFoo(B pthis) {
>>>>     /* method for B */
>>>> }
>>>>
>>>> Then if you call doFoo(new A()) the call will become A_doFoo(new A()), if you call doFoo(new B()) the call will become B_doFoo(new B()), if you call doFoo(new C()) the call will become B_doFoo(new C()).
>>>>
>>>> If anybody has some improvements, that'd be cool. Maybe you can get rid of the dependence on string mixins ... but I don't think templates quite cut it.
>>>>
>>>>  - Gregor Richards
>>>>
>>>
>>> Pretty cool stuff, but I don't see how this is at all better than the visitor pattern. It is not checked at compile-time (so if you forget to implement one part of the hierarchy, you won't find that out until runtime) and it's likely less efficient, especially for large enough hierarchies (i.e. syntax trees). Where's the happy?
>>
>> The visitor pattern requires annotating every class in the hierarchy. That's what annoys me about it. I'd like my AST nodes to just be AST nodes.
>>
>> Yes, calling a function is less efficient. It can probably be improved, I'm just not sure how yet :P
>>
>> Plus, of course, although the visitor pattern is the example I gave, this is a more general mechanism.
>>
>>  - Gregor Richards
>
> I agree that this has other uses, so mad props for making it.
>
> For the visitor pattern, I wish there was a "dynamic" type like in C#4. This way, each node needs only to be annotated once for visitors, no matter what they return (with variadic arguments used for parameters, too).
>

I'd rather have a type that all other types derive from. Sort of like "Object" (or is it "object"?), but serves as the base type for all types, not just classes. Not sure if this would be possible though (having vtables for every int and char in the program doesn't sound like a good idea ;) ).

> Unrelated (but the main reason I use visitors), I wish D could allow could declaring a virtual function in the class definition but implement it elsewhere (a la C++). So...
>
> module1.d:
> ----------
> module module1;
> class A { int foo(int x); }
>
> module 2.d:
> -----------
> module module2;
> import module1;
> int A.foo(int x) { return x; }

Sounds like you're thinking of something like C#'s partial classes, but with the ability to say "Compiling should fail if I've forgotten to define bodies for any of the following functions"?


December 07, 2008
Nick Sabalausky wrote:
>> Unrelated (but the main reason I use visitors), I wish D could allow could declaring a virtual function in the class definition but implement it elsewhere (a la C++). So...
>>
>> module1.d:
>> ----------
>> module module1;
>> class A { int foo(int x); }
>>
>> module 2.d:
>> -----------
>> module module2;
>> import module1;
>> int A.foo(int x) { return x; }
> 
> Sounds like you're thinking of something like C#'s partial classes, but with the ability to say "Compiling should fail if I've forgotten to define bodies for any of the following functions"? 

That's one way to think about it. I was thinking that in a C++ header file you o this:

class A { public: int foo(int x); }

And in another file you do this:

#include "A.h"
int A::foo(int x) { }

If foo is not implemented, the failure is at link time.
December 07, 2008
I had done something similar, albeit using D2 and traits, in http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=56018

It's good to see it's possible in D1! The main advantage of __traits seems to be that you can avoid building a lookup table and thereby simplify the user interface. On the other hand, your approach will probably work even when the extension methods are defined in different modules, as long as the one with the extensions mixin is imported?

« First   ‹ Prev
1 2