www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Mixin replacement for switch...case?

reply "Jerome" <jerome.spamable yahoo.com> writes:
Hi!

This is a question from a complete newbie.

Is there a way to replace switch...case statements by a mixin 
template, maybe a variadic mixin template (does such a thing 
exist?).

What I would want to achieve is to have this kind of syntax:

mixin Select!(value,
   if0, { then0(); },
   if1, { then1(); },
   if2, { foo(); bar(); },
   { thenDefault(); }
);

to replace this:

switch(value) {
   case if0 : { then0(); } break;
   case if1 : { then1(); } break;
   case if2 : { foo(); bar(); } break;
   default : thenDefault();
}

The reason I ask this is because I almost never use fall through 
and the verbosity of the switch statement has been driving me 
crazy.
Oct 22 2012
next sibling parent reply "Jerome" <jerome.spamable yahoo.com> writes:
No answer. Should I assume that it is not possible?
That's something that could be done in C with a simple macro. I 
really would like to know to what extent mixins are a replacement 
for C macros for generating boilerplate code.
Oct 23 2012
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 10/23/2012 11:47 AM, Jerome wrote:
 No answer. Should I assume that it is not possible?

If you like to be mistaken, feel free to do that. http://en.wikipedia.org/wiki/Non_sequitur_%28logic%29
 That's something that could be done in C with a simple macro. I really
 would like to know to what extent mixins are a replacement for C macros
 for generating boilerplate code.

Use string mixins and templates for that if you have to. They are better suited for code generation than C macros. If your goal is to obfuscate the program, C macros will help you more.
Oct 23 2012
prev sibling next sibling parent "Jerome" <jerome.spamable yahoo.com> writes:
OK. I have done my homework and answered my own question based on 
the Duff's Device example in the Language Reference page for 
Mixins.

The solution (not variadic though) would be:

mixin template Select!(alias value,
   alias if0, alias then0,
   alias if1, alias then1,
   alias if2, alias then2,
   alias thenDefault)
{
   switch(value) {
     case if0 : { then0(); } break;
     case if1 : { then1(); } break;
     case if2 : { then2(); } break;
     default : thenDefault();
   }
}

and it is used this way:

mixin Select!(value,
   if0, delegate { then0(); },
   if1, delegate { then1(); },
   if2, delegate { foo(); bar(); },
   delegate { thenDefault(); }
);

no gain at all verbosity-wise I'm afraid... nevermind.
Oct 23 2012
prev sibling next sibling parent "Daniel =?UTF-8?B?S296w6FrIg==?= <kozzi11 gmail.com> writes:
I think this should be possible, look for eg. to std.bitmanip 
bitfields template

On Tuesday, 23 October 2012 at 09:47:55 UTC, Jerome wrote:
 No answer. Should I assume that it is not possible?
 That's something that could be done in C with a simple macro. I 
 really would like to know to what extent mixins are a 
 replacement for C macros for generating boilerplate code.

Oct 23 2012
prev sibling next sibling parent "Jerome" <jerome.spamable yahoo.com> writes:
On Tuesday, 23 October 2012 at 10:40:12 UTC, Daniel Kozák wrote:
 I think this should be possible, look for eg. to std.bitmanip 
 bitfields template

 On Tuesday, 23 October 2012 at 09:47:55 UTC, Jerome wrote:
 No answer. Should I assume that it is not possible?
 That's something that could be done in C with a simple macro. 
 I really would like to know to what extent mixins are a 
 replacement for C macros for generating boilerplate code.


Thanks Daniel! It's exactly what I was looking for. ;-)
Oct 23 2012
prev sibling next sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
 The solution (not variadic though) would be:

 mixin template Select!(alias value,
   alias if0, alias then0,
   alias if1, alias then1,
   alias if2, alias then2,
   alias thenDefault)

 {
   switch(value) {
     case if0 : { then0(); } break;
     case if1 : { then1(); } break;
     case if2 : { then2(); } break;
     default : thenDefault();
   }
 }

You can get something interesting a few lines of code: template select(cases...) if (cases.length % 2 == 1) { auto select(Input)(Input input) { static if (cases.length == 1) // Default case return cases[0](); else // standard case { if (input == cases[0]) return cases[1](); else return .select!(cases[2..$])(input); } } } void main() { // With block delegates alias select!(0, { writeln("Zero.");}, 1, { writeln("One."); }, { writeln("Something else.");}) counter; counter(0); counter(1); counter(10_000); // With anonymous functions: alias select!(0, ()=> "Zero.", 1, ()=> "One.", ()=> "Something else.") counter2; writeln(counter2(0)); writeln(counter2(1)); writeln(counter2(10_000)); } A slightly more generic version takes predicates as first arguments, as Lisp's cond form: template cond(cases...) if (cases.length % 2 == 1) { auto cond(Input)(Input input) { static if (cases.length == 1) // Default case return cases[0](); else // standard case { if (cases[0](input)) // The only difference with select return cases[1](); else return .cond!(cases[2..$])(input); } } } void main() { alias cond!((a) => a < 0, ()=> "Negative.", (a) => a > 0, ()=> "Positive.", ()=> "Zero.") counter3; writeln(counter3(-10)); writeln(counter3(1)); writeln(counter3(0)); } Philippe
Oct 23 2012
prev sibling next sibling parent "Jerome" <jerome.spamable yahoo.com> writes:
Thanks Philippe! Great solution!

I have two remarks.

Remark 1: I understand that your mixin will be expanded into 
cascaded if...else statements. It would probably be more 
efficient to expand into switch...case, don't you think?

Remark 2: I infer from your code that the "delegate" keyword is 
not mandatory, so my solution could also be called like this:

mixin Select!(value,
   if0, { then0(); },
   if1, { then1(); },
   if2, { foo(); bar(); },
   { thenDefault(); }
);

instead of:

mixin Select!(value,
   if0, delegate { then0(); },
   if1, delegate { then1(); },
   if2, delegate { foo(); bar(); },
   delegate { thenDefault(); }
);

Is that correct?
Oct 24 2012
prev sibling next sibling parent "Jerome" <jerome.spamable yahoo.com> writes:
 Remark 1: I understand that your mixin will be expanded into 
 cascaded if...else statements. It would probably be more 
 efficient to expand into switch...case, don't you think?

Oh! I've just figured out that it is not a mixin, but a function template.
Oct 24 2012
prev sibling next sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
On Wed, Oct 24, 2012 at 9:53 AM, Jerome <jerome.spamable yahoo.com> wrote:
 Thanks Philippe! Great solution!

 I have two remarks.

 Remark 1: I understand that your mixin will be expanded into cascaded
 if...else statements. It would probably be more efficient to expand into
 switch...case, don't you think?

Probably, but my solution can be generalized further, to provide a sort of pattern-matching: template match(cases...) { auto match(Input...)(Input input) { static if (cases.length == 0) static assert(false, "No match for args of type "~ Input.stringof); else static if (__traits(compiles, cases[0](input))) // Can we call cases[0] on input? return cases[0](input); // If yes, do it else // else, recurse farther down return .match1!(cases[1..$])(input); } } string more(T...)(T t){ return "More than two args. Isn't life wonderful?";} void main() { alias match!( () => "No args", (a) => "One arg, of type " ~ typeof(a).stringof ~ " with value: " ~ to!string(a), (a, string b)=> "Two args (" ~ to!string(a) ~ ", " ~ to!string(b) ~ "). I know the second one is a string.", (a, b) => "Two args", more ) matcher; writeln(matcher()); writeln(matcher(3.1416)); writeln(matcher(1, "abc")); writeln(matcher(1, 1)); writeln(matcher(1, "abc", 3.1416)); writeln(matcher(1,1,1,1,1)); } As you can see, different branches are selected based on the number and type of arguments. This is quite powerful: auto-detection based on the number of args, using the short syntax for function templates (args ) => result Only for `more` did I need to define an external function. Of course, standard (non-templated) functions can be used too. The only limitation is that all branches must return the same type, as for a stand switch... case statement. But even this can be circumvented. The code is longer, I paste is there: http://dpaste.dzfl.pl/c315a160 usage: void main() { alias match!( () => 3.14159, (a) => "One arg, of type " ~ typeof(a).stringof ~ " with value: " ~ to!string(a), (a, string b)=> "Two args (" ~ to!string(a) ~ ", " ~ to!string(b) ~ "). I know the second one is a string.", (a, b) => 0, more ) matcher; writeln(matcher()); writeln(matcher(3.1416)); writeln(matcher(1, "abc")); writeln(matcher(1, 1)); writeln(matcher(1, "abc", 3.1416)); writeln(matcher(1,1,1,1,1)); } Different argument lists, different result types!
 Remark 2: I infer from your code that the "delegate" keyword is not
 mandatory, so my solution could also be called like this:


 mixin Select!(value,
   if0, { then0(); },
   if1, { then1(); },
   if2, { foo(); bar(); },
   { thenDefault(); }
 );

 instead of:


 mixin Select!(value,
   if0, delegate { then0(); },
   if1, delegate { then1(); },
   if2, delegate { foo(); bar(); },
   delegate { thenDefault(); }
 );

 Is that correct?

Yes, it is. code blocks are void delegate()'s in D, or T delegate() with a return statement: { writeln("Hello World!"); return 0;} is an int delegate(). You can also use the short delegate syntax: mixin Select!(value, if0, () => then0(), if1, () => then1(), if2, () => (foo(), bar()), () => thenDefault() ); Notice that, in your previous example 'value' is a compile-time value.My examples were made so as to permit runtime arguments.
Oct 24 2012
prev sibling next sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
Damn, I typed this a bit too fast. I forgot the imports:


 Probably, but my solution can be generalized further, to provide a
 sort of pattern-matching:

Add: import std.conv; import std.stdio;
 template match(cases...)
 {
     auto match(Input...)(Input input)
     {
         static if (cases.length == 0)
             static assert(false, "No match for args of type "~ Input.stringof);
         else static if (__traits(compiles, cases[0](input))) // Can we
 call cases[0] on input?
             return cases[0](input); // If yes, do it
         else // else, recurse farther down
             return .match1!(cases[1..$])(input);
     }
 }

 string more(T...)(T t){ return "More than two args. Isn't life wonderful?";}



 void main()
 {
     alias match!(
         ()  => "No args",
         (a) => "One arg, of type " ~ typeof(a).stringof ~ " with
 value: " ~ to!string(a),
         (a, string b)=> "Two args (" ~ to!string(a) ~ ", " ~
 to!string(b) ~ "). I know the second one is a string.",
         (a, b) => "Two args",
         more
     ) matcher;

     writeln(matcher());
     writeln(matcher(3.1416));
     writeln(matcher(1, "abc"));
     writeln(matcher(1, 1));
     writeln(matcher(1, "abc", 3.1416));
     writeln(matcher(1,1,1,1,1));
 }

Oct 24 2012
prev sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
On Wed, Oct 24, 2012 at 9:56 AM, Jerome <jerome.spamable yahoo.com> wrote:
 Remark 1: I understand that your mixin will be expanded into cascaded
 if...else statements. It would probably be more efficient to expand into
 switch...case, don't you think?

Oh! I've just figured out that it is not a mixin, but a function template.

That was to allow runtime arguments to be used. In your example, value is a compile-time argument. In this case, all tests can be done at CT and your code should result *only* in the right branch: no need to develop an entire switch statement.
Oct 24 2012