www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Composing features at compile time

reply Jacob Carlborg <doob me.com> writes:
Does anyone know a good way of composing features at compile time? Say I 
have a struct or class that I want to support different features that 
are configurable at compile time. One way would be to just pass in a 
couple of boolean flags and use static-if, like this:

class Foo (bool logging, bool events)
{
     static if (logging)
         Logger logger;

     this ()
     {
         static if (logging)
             logger = new Logger;
     }

     void action ()
     {
         static if (logging)
             logger.info("performing action");
         // perform action
     }
}

Using this approach I get the feeling that there will quickly become a 
big nest of static-ifs. Does anyone have any better ideas?

-- 
/Jacob Carlborg
Nov 24 2013
next sibling parent reply =?UTF-8?B?IlLDqW15IE1vdcOremEi?= <remy.moueza gmail.com> writes:
This looks like a case study for aspect oriented programming: 
several separated concerns that start to intertwine within your 
code; if left unchecked this could result in some messy code.

In Python, I used reflection and "magic" things and have already 
used the spring AOP in Java that is done with some Aspect classes 
and annotation. I suppose that D could bring the best of both 
world with its compile time reflection and UDA.

But if you are in urgent need for a solution, here is a C++ trick 
called "mixin classes", template classes parameterized on their 
base class:

interface IFoo {
     void action ();
}

class Foo : IFoo
{
     this () {}

     void action ()
     {
         // perform action
     }
}

class FooLogging (T : IFoo) : T {
     Logger logger;

     this () {
         logger = new Logger;
     }

     override void action ()
     {
         logger.info("performing action");
         super.action(); // perform action
     }

}

void main () {
     Foo foo;

     static if (logging)
         foo = new FooLogging!Foo;
     else
         foo = new Foo;

     foo.action ();
}

You get separation of concerns: the logging features are in 
FooLogging, overriding the basic behaviour in Foo; you can add a 
FooEvent mixin class to deal with events and so on to deal with 
any concerns you need.

The "weaving" is done in the main() function in my example. You 
could prefer to put a big `static if (logging)` in the FooLogging 
class body and always instanciate foo instances with the full 
chain of mixin classes (foo = new 
FooLogging!(FooEvent!(FooAuthentication!(...!Foo)))), or anything 
else that fits you better.

This works nicely if your "aspect" (Logging, Events...) applies 
on the whole action() method. Otherwise, you'll have to cut 
action() into smaller methods that will have to be decorated in 
the appropriate mixin class(es) (as the Strategy design pattern).

I am eager to know if anybody else has a better (or more concise) 
solution.
I hope this helps.

On Sunday, 24 November 2013 at 13:18:15 UTC, Jacob Carlborg wrote:
 Does anyone know a good way of composing features at compile 
 time? Say I have a struct or class that I want to support 
 different features that are configurable at compile time. One 
 way would be to just pass in a couple of boolean flags and use 
 static-if, like this:

 class Foo (bool logging, bool events)
 {
     static if (logging)
         Logger logger;

     this ()
     {
         static if (logging)
             logger = new Logger;
     }

     void action ()
     {
         static if (logging)
             logger.info("performing action");
         // perform action
     }
 }

 Using this approach I get the feeling that there will quickly 
 become a big nest of static-ifs. Does anyone have any better 
 ideas?
Nov 24 2013
parent reply Jacob Carlborg <doob me.com> writes:
On 2013-11-24 15:06, "Rémy Mouëza" wrote:
 This looks like a case study for aspect oriented programming: several
 separated concerns that start to intertwine within your code; if left
 unchecked this could result in some messy code.

 In Python, I used reflection and "magic" things and have already used
 the spring AOP in Java that is done with some Aspect classes and
 annotation. I suppose that D could bring the best of both world with its
 compile time reflection and UDA.

 But if you are in urgent need for a solution, here is a C++ trick called
 "mixin classes", template classes parameterized on their base class:

 interface IFoo {
      void action ();
 }

 class Foo : IFoo
 {
      this () {}

      void action ()
      {
          // perform action
      }
 }

 class FooLogging (T : IFoo) : T {
      Logger logger;

      this () {
          logger = new Logger;
      }

      override void action ()
      {
          logger.info("performing action");
          super.action(); // perform action
      }

 }

 void main () {
      Foo foo;

      static if (logging)
          foo = new FooLogging!Foo;
      else
          foo = new Foo;

      foo.action ();
 }

 You get separation of concerns: the logging features are in FooLogging,
 overriding the basic behaviour in Foo; you can add a FooEvent mixin
 class to deal with events and so on to deal with any concerns you need.

 The "weaving" is done in the main() function in my example. You could
 prefer to put a big `static if (logging)` in the FooLogging class body
 and always instanciate foo instances with the full chain of mixin
 classes (foo = new FooLogging!(FooEvent!(FooAuthentication!(...!Foo)))),
 or anything else that fits you better.
I thought of doing something similar with template mixins.
 This works nicely if your "aspect" (Logging, Events...) applies on the
 whole action() method. Otherwise, you'll have to cut action() into
 smaller methods that will have to be decorated in the appropriate mixin
 class(es) (as the Strategy design pattern).
Unfortunately logging and events were more of an example, for the real usage it's not as easy to add before or after actions.
 I am eager to know if anybody else has a better (or more concise) solution.
 I hope this helps.
-- /Jacob Carlborg
Nov 24 2013
parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
 Unfortunately logging and events were more of an example, for the real usage
Ah then, you can discard my own proposals. Only #3 is still possible. Try not to think to much of AST macros :-)
Nov 24 2013
prev sibling next sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
The general problem is to inject code at predetermined points. As Rémy
said, that should remind us of AOP.

I have three other very vague ideas ;-) Not tested in any way.

#1) Create a wrapper, say Interceptor!(Foo, "__ctor", "bar").
Interceptor will act as a Foo, except it will have hooks that are
called before and after any calls to "__ctor" (so Foo.this()) and
Foo.bar.

The good:  I imagine the hooks can be changed at runtime. By default,
they do nothing.
The bad: Interceptor!(Foo) is not a Foo anymore. But then, your own
Foo!(option0) is not related to Foo!(option1) also.
Bad #2: no hook inside a method, they are only called before or after a method.
The ugly: the pre- and post-hooks could all have different types :-(
(from void delegate() to... whatever).

#2) Same idea, but the intercepting code is inside Foo (maybe with a
mixin?). The real methods are private, and opDispatch catch the calls,
calls the pre-hook, then the inner method, then the post-hook.


#3) The string mixin road. I know what you're thinking. Learn to love
the strings!
Define Foo only as a string to be compiled, with some points where
string interpolation is possible.
string makeFoo(string hooks()...) { return "struct Foo { " ~ hook[0]() ~ ... }

The good: you can now inject parameterized code anywhere you want into Foo code.
The bad: well, string mixins. You're coding blind.
Nov 24 2013
prev sibling next sibling parent reply "Dicebot" <public dicebot.lv> writes:
Well you can make one step forward and use policy-based design as 
described in Andrei's old C++ book ;) (replace boolean flags with 
template aliases to mixin, providing stub ones for "false") It 
does not fix `static-if` sequence but scales better in terms of 
configurability.
Nov 24 2013
parent reply Philippe Sigaud <philippe.sigaud gmail.com> writes:
But how do you use policies to inject different code in different
places of your struct?

On Sun, Nov 24, 2013 at 3:15 PM, Dicebot <public dicebot.lv> wrote:
 Well you can make one step forward and use policy-based design as described
 in Andrei's old C++ book ;) (replace boolean flags with template aliases to
 mixin, providing stub ones for "false") It does not fix `static-if` sequence
 but scales better in terms of configurability.
Nov 24 2013
parent reply "Dicebot" <public dicebot.lv> writes:
On Sunday, 24 November 2013 at 14:23:55 UTC, Philippe Sigaud 
wrote:
 But how do you use policies to inject different code in 
 different
 places of your struct?
Don't really get the question. Isn't it what policies do pretty much by definition? You decompose part of target functionality into smaller blocks with optional behavior (including no-op behavior) and provide specific ones as alias parameters. D has additional benefit of being able to use one as template mixin for cleaner syntax. But I am pretty sure you know this, so what is the question again? :)
Nov 24 2013
next sibling parent reply "Dicebot" <public dicebot.lv> writes:
On Sunday, 24 November 2013 at 17:25:49 UTC, Dicebot wrote:
 Don't really get the question. Isn't it what policies do pretty 
 much by definition? You decompose part of target functionality 
 into smaller blocks with optional behavior (including no-op 
 behavior) and provide specific ones as alias parameters. D has 
 additional benefit of being able to use one as template mixin 
 for cleaner syntax. But I am pretty sure you know this, so what 
 is the question again? :)
To clarify, OP example will look like this: class Foo (alias Logger) if (isLoggger!Logger) { private Logger logger; this () { logger = new Logger; } void action () { logger.info("performing action"); // perform action } } // ... auto foo = new Foo!StubLogger(); // "false" case It works better with compilers like LDC of course, because of good inlining and AST restructuring during optimization pass.
Nov 24 2013
parent reply Jacob Carlborg <doob me.com> writes:
On 2013-11-24 18:31, Dicebot wrote:

 To clarify, OP example will look like this:

 class Foo (alias Logger)
      if (isLoggger!Logger)
 {
       private Logger logger;

       this ()
       {
           logger = new Logger;
       }

       void action ()
       {
           logger.info("performing action");
           // perform action
       }
 }

 // ...

 auto foo = new Foo!StubLogger(); // "false" case
So StubLogger would implement "info" but do nothing? How good would the compiler be at optimizing this?
 It works better with compilers like LDC of course, because of good
 inlining and AST restructuring during optimization pass.
-- /Jacob Carlborg
Nov 24 2013
parent "Dicebot" <public dicebot.lv> writes:
On Sunday, 24 November 2013 at 19:37:44 UTC, Jacob Carlborg wrote:
 So StubLogger would implement "info" but do nothing? How good 
 would the compiler be at optimizing this?
Last time I checked such stuff LDC is quite capable at eliminating it completely. It works somewhat better for state-less stuff though as you still get extra pointer overhead because of instantiated field. If you could call static methods instead, only overhead will be of few extra unused symbols in binary.
Nov 24 2013
prev sibling parent reply Philippe Sigaud <philippe.sigaud gmail.com> writes:
On Sun, Nov 24, 2013 at 6:25 PM, Dicebot <public dicebot.lv> wrote:
 On Sunday, 24 November 2013 at 14:23:55 UTC, Philippe Sigaud wrote:
 But how do you use policies to inject different code in different
 places of your struct?
Don't really get the question. Isn't it what policies do pretty much by definition? You decompose part of target functionality into smaller blocks with optional behavior (including no-op behavior) and provide specific ones as alias parameters. D has additional benefit of being able to use one as template mixin for cleaner syntax. But I am pretty sure you know this, so what is the question again? :)
Hmm. Then you still need to define where the policy will act. OK, I can get that. I thought the OP wanted something more flexible, where different loggers could inject code at many different places. But if there is no Logging policy asked for (the user doesn't care for logging), you must insure that the default policy 'disappears', so to speak. I'm not sure that's always possible.
 auto foo = new Foo!StubLogger(); // "false" case
So StubLogger would implement "info" but do nothing? How good would the compiler be at optimizing this?
Exactly.
Nov 24 2013
parent Jacob Carlborg <doob me.com> writes:
On 2013-11-24 20:53, Philippe Sigaud wrote:

 Hmm. Then you still need to define where the policy will act. OK, I
 can get that. I thought the OP wanted something more flexible, where
 different loggers could inject code at many different places.

 But if there is no Logging policy asked for (the user doesn't care for
 logging), you must insure that the default policy 'disappears', so to
 speak. I'm not sure that's always possible.
Please see this post for an actual problem I'm trying to solve: http://forum.dlang.org/thread/l6suan$22f4$1 digitalmars.com?page=2#post-l6tlhj:242ms1:241:40digitalmars.com -- /Jacob Carlborg
Nov 24 2013
prev sibling next sibling parent reply Dejan Lekic <dejan.lekic gmail.com> writes:
Jacob Carlborg wrote:

 Does anyone know a good way of composing features at compile time? Say I
 have a struct or class that I want to support different features that
 are configurable at compile time. One way would be to just pass in a
 couple of boolean flags and use static-if, like this:
 
 class Foo (bool logging, bool events)
 {
      static if (logging)
          Logger logger;
 
      this ()
      {
          static if (logging)
              logger = new Logger;
      }
 
      void action ()
      {
          static if (logging)
              logger.info("performing action");
          // perform action
      }
 }
 
 Using this approach I get the feeling that there will quickly become a
 big nest of static-ifs. Does anyone have any better ideas?
 
Jakob, whenever I need something like you describe, I do more/less the same what is described on this Wikipedia page: http://en.wikipedia.org/wiki/Composition_over_inheritance . C# example is exactly how I do this (in Java and D). -- Dejan Lekic dejan.lekic (a) gmail.com http://dejan.lekic.org
Nov 24 2013
parent reply Jacob Carlborg <doob me.com> writes:
On 2013-11-24 19:28, Dejan Lekic wrote:

 Jakob, whenever I need something like you describe, I do more/less the same
what
 is described on this Wikipedia page:
 http://en.wikipedia.org/wiki/Composition_over_inheritance . C# example is
 exactly how I do this (in Java and D).
Unfortunately this looks like it works best when providing fairly separate features that doesn't interact with each other or the main functionality. Perhaps I should describe the real usage I have for this. I'm trying to figure out a good way to make std.serialization more composeable without any overhead for unwanted features. One example is this function: http://pastebin.com/XYGtTarn Lines 19-25, 34-37 and 42 are only needed if pointers are supported. Lines 27-30 are only needed if references are supported. There are already both a couple of static-ifs and regular ifs. Adding even more static-ifs will make the code a bigger mess than it already is. -- /Jacob Carlborg
Nov 24 2013
parent reply Philippe Sigaud <philippe.sigaud gmail.com> writes:
Unfortunately this looks like it works best when providing fairly separate
features
 that doesn't interact with each other or the main functionality.
If features are coupled, I see no easy way out.
 http://pastebin.com/XYGtTarn

 Lines 19-25, 34-37 and 42 are only needed if pointers are supported.
 Lines 27-30 are only needed if references are supported.

 There are already both a couple of static-ifs and regular ifs. Adding even
 more static-ifs will make the code a bigger mess than it already is.
If references are not supported, you want the else clause all by itself? (lignes 34-43)
Nov 24 2013
parent reply Jacob Carlborg <doob me.com> writes:
On 2013-11-24 21:14, Philippe Sigaud wrote:

 If features are coupled, I see no easy way out.
I suspected that. They not so much coupled with each other, rather coupled with the default functionality that will always be there.
 If references are not supported, you want the else clause all by
 itself? (lignes 34-43)
Yes, exactly. -- /Jacob Carlborg
Nov 24 2013
parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
On Sun, Nov 24, 2013 at 9:44 PM, Jacob Carlborg <doob me.com> wrote:
 On 2013-11-24 21:14, Philippe Sigaud wrote:

 If features are coupled, I see no easy way out.
I suspected that. They not so much coupled with each other, rather coupled with the default functionality that will always be there.
I don't see how to do that, except by using flags & string mixins. It'll still be the same intricate (and not so easily extensible) code, but at least by using string mixins the functionality can be defined elsewhere and reused. I suppose that the standard advice would be to decouple features as much as possible, but then you probably already do that :)
Nov 24 2013
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
On 2013-11-24 14:18, Jacob Carlborg wrote:
 Does anyone know a good way of composing features at compile time? Say I
 have a struct or class that I want to support different features that
 are configurable at compile time. One way would be to just pass in a
 couple of boolean flags and use static-if, like this:
Here's an actual problem I'm trying to solve: http://forum.dlang.org/thread/l6suan$22f4$1 digitalmars.com?page=2#post-l6tlhj:242ms1:241:40digitalmars.com -- /Jacob Carlborg
Nov 24 2013
prev sibling parent Shammah Chancellor <anonymous coward.com> writes:
Based on what your actual problem is -- it seems like you need to 
refactor your code a little.   Also,  you should trust that the 
compiler optimizes correctly.

eg.

if( valueMeta.isValid && pointersSupported)  should be optimized out 
when pointerSupported == false and the comparison of it should be 
optimized out when it is true.
Nov 24 2013