digitalmars.D - cool pattern matching template
- Vlad Levenfeld (68/68) Dec 24 2014 Hey all, I've found myself leaning on a really versatile pattern
- Phil (3/17) Jan 01 2015 Could you give a concrete example of using your Match template?
- Vlad Levenfeld (28/31) Jan 01 2015 auto f (T)(T n)
- Phil (2/2) Jan 01 2015 Thanks for the example. I'd not seen the trick with empty type
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
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
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
Thanks for the example. I'd not seen the trick with empty type parameter lists before.
Jan 01 2015