www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - cool pattern matching template

reply "Vlad Levenfeld" <vlevenfeld gmail.com> writes:
Hey all, I've found myself leaning on a really versatile pattern
a lot lately, thought I'd share it: this is for those situations
where you might have something like

    static if (is (typeof(some stuff)))
      return some stuff;
    else static if (__traits(compiles, more things))
      return more things;

or perhaps some other repetitive or confusing piece of
metaprogramming logic whose goal is to decide what code to
execute.

instead, try:

    auto option_a ()() {return some stuff;}
    auto option_b ()() {return more things;}

    return Match!(option_a, option_b);

where

    template Match (T...) {
      alias Invoke (alias U) = U!();
      enum compiles (U...) = __traits(compiles, Invoke!(U[0]));
      alias Filtered = Filter!(compiles, T);

      static if (Filtered.length > 0)
        alias Match = Invoke!(Filtered[0]);
      else static assert (0);
    }

the Match template will pick the first symbol in the list that
successfully instantiates, then alias itself to the result of the
instantiation.

it can also be used to pattern-match on types, enums, or aliases:

    template MyTemp (List) {

      enum get_list_item () = List[i];
      enum get_list_temp () = List!i;

      enum MyTemp = Match!(get_list_item, get_list_temp);
    }

adding a no-op to the end of the pattern list allows Match to
fail quietly, if this is desired:

     void a ()(){some stuff;}
     void b ()(){more stuff;}
     void no_op ()() {}

     Match!(a, b, no_op);

staticMap + nested template functions can also be used to create
arbitrary runtime value tuples, which combines with Match in a
natural way:

     auto a (uint i)() {
       auto b ()() {...}
       auto c ()() {...}

       return Match!(b,c);
     }

     return func_call (staticMap!(a, staticIota!(0,n)));

supposing that func_call is an n-ary function, then this code
will call it with the best values according to the specified
matching priorities.

It can also be used to control mixin overload sets:

    template MixA () {auto func (T args) {...}}
    template MixB () {auto func (U args) {...}}

    struct MyStruct {
      mixin MixA!(...) A;
      mixin MixB!(...) B;

      auto func (V args) {
        auto a ()() {return A.func (args);}
        auto b ()() {return B.func (args);}

        return Match!(b,a);
      }
    }

here we control the routing for func by prioritizing B's func
over A's.

I've been repeatedly surprised at how many places this pattern
fits, and with how much code it has cleaned up, replacing lots of
noisy static if-else ladders and string mixins and traits logic
with some descriptively-named snippets and a call to Match.
Dec 24 2014
parent reply "Phil" <phil.j.ellison gmail.com> writes:
Could you give a concrete example of using your Match template?

e.g. to perform a different action for floating point or integral 
values.

 Hey all, I've found myself leaning on a really versatile pattern
 a lot lately, thought I'd share it: this is for those situations
 where you might have something like

    static if (is (typeof(some stuff)))
      return some stuff;
    else static if (__traits(compiles, more things))
      return more things;

 or perhaps some other repetitive or confusing piece of
 metaprogramming logic whose goal is to decide what code to
 execute.

 instead, try:

    auto option_a ()() {return some stuff;}
    auto option_b ()() {return more things;}

    return Match!(option_a, option_b);
Jan 01 2015
parent reply "Vlad Levenfeld" <vlevenfeld gmail.com> writes:
On Thursday, 1 January 2015 at 19:54:56 UTC, Phil wrote:
 Could you give a concrete example of using your Match template?

 e.g. to perform a different action for floating point or 
 integral values.
auto f (T)(T n) { auto integral ()() {return integral_only_func (n);} auto floating_point ()() {return float_func (n);} return Match!(integral, floating_point); } Assuming the integral_only_func rejects floating point numbers, then Match will attempt the integral version first and, if it fails, fall back on float_func. Matching an explicitly defined list of patterns, as above, is the most common way I use it, though some more interesting examples come up from time to time. Here's an example from a project of mine, using Match in-line to deduce the length of some vertex arrays attached to an openGL shader struct. It assumes all attached arrays have the same length. The template constraint prevents accidentally grabbing the length of a uniform vector argument. template length (uint i) { auto length ()() if (not (is (typeof(shader.args[i]) == Vector!U, U...))) {return shader.args[i].length.to!int;} } gl.DrawArrays (shader.mode, 0, Match!(Map!(length, Count!(Shader.Args)))); (where Count!T means staticIota!(0, T.length) and Map is an alias of staticMap)
Jan 01 2015
parent "Phil" <phil.j.ellison gmail.com> writes:
Thanks for the example. I'd not seen the trick with empty type 
parameter lists before.
Jan 01 2015