www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - A lightweight module for class extensions in D

reply Gregor Richards <Richards codu.org> writes:
Content-Type: text/plain

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
Dec 05 2008
next sibling parent reply Robert Fraser <fraserofthenight gmail.com> writes:
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?
Dec 05 2008
parent reply Gregor Richards <Richards codu.org> writes:
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
Dec 05 2008
parent reply Robert Fraser <fraserofthenight gmail.com> writes:
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; }
Dec 05 2008
parent reply "Nick Sabalausky" <a a.a> writes:
"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"?
Dec 06 2008
next sibling parent Robert Fraser <fraserofthenight gmail.com> writes:
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.
Dec 06 2008
prev sibling parent reply Christopher Wright <dhasenan gmail.com> writes:
Nick Sabalausky wrote:
 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 ;) ).

This would be convenient. I think, if Walter were for this idea, it'd be implemented via automatic boxing. That gives you the efficiency of using int, double, and so forth directly on the hardware. (It's how C# does it.) That said, I seriously doubt this will be a priority for Walter, ever.
Dec 07 2008
parent "Nick Sabalausky" <a a.a> writes:
"Christopher Wright" <dhasenan gmail.com> wrote in message 
news:ghgikj$2ckq$1 digitalmars.com...
 Nick Sabalausky wrote:
 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 
 ;) ).

This would be convenient. I think, if Walter were for this idea, it'd be implemented via automatic boxing. That gives you the efficiency of using int, double, and so forth directly on the hardware. (It's how C# does it.) That said, I seriously doubt this will be a priority for Walter, ever.

I came across a need for it in a command-line parser I'm writing, but ended up having to define a boxing class myself ("RefBox!(T)", which can then be cast to "Object", although I should probably create a special base class for all the "RefBox!(T)"'s) and use a string mixin to clean up usage. I would much rather use a standardized boxer though. A "dynamic/variant" type would probably work too, but I don't like doing any runtime type variance other than polymorphism and boxing. If anyone's interested, my code is attached. I think the only part of my util module this uses is "stformat()" which is nothing more than the following helper code for Tango's Layout!(T): Layout!(char) stformat; static this() { stformat = new Layout!(char)(); }
Dec 07 2008
prev sibling next sibling parent Christopher Wright <dhasenan gmail.com> writes:
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;`); } }
Dec 05 2008
prev sibling next sibling parent "Nick Sabalausky" <a a.a> writes:
"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*) ;)
Dec 06 2008
prev sibling next sibling parent Janderson <ask me.com> writes:
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
Dec 06 2008
prev sibling parent reply Christian Kamm <kamm-incasoftware removethis.de> writes:
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?
Dec 07 2008
parent Gregor Richards <Richards codu.org> writes:
Christian Kamm wrote:
 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?
 

Yes. The best solution IMHO would be to be able to declare things to be in the vtable that aren't functions (but are, for example, a pointer to some area of globally-accessible-and-writable memory). Then you could effectively have a sub-vtable. But alas, 'tis not possible :) - Gregor Richards
Dec 07 2008