www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - static switch/pattern matching

reply John <johnch_atms hotmail.com> writes:
Writing a long series of "static if ... else" statements can be 
tedious and I'm prone to leaving out the crucial "static" after 
"else", so I was wondered if it was possible to write a template 
that would resemble the switch statement, but for types.

Closest I came up to was this:

   void match(T, Fs...)() {
     foreach (F; Fs) {
       static if (isFunctionPointer!F) {
         alias Ps = Parameters!F;
         static if (Ps.length == 1) {
           static if (is(Ps[0] == T)) F(Ps[0].init);
         }
       }
     }
   }

   void test(T)(T t) {
     match!(T,
       (int _) => writeln("Matched int"),
       (string _) => writeln("Matched string")
     );
   }

But that's pretty limited and I'd like to be able to match on 
whether a type derives from T as well. I just can't figure it out.

Something like this would be ideal...

   match!(T,
     int => writeln("Matched int"),
     is(T : SomeObject) => writeln("Derives from SomeObject")
   );

Anyone able to improve on it?
Jun 25 2016
next sibling parent reply Lodovico Giaretta <lodovico giaretart.net> writes:
On Saturday, 25 June 2016 at 08:46:05 UTC, John wrote:
 Writing a long series of "static if ... else" statements can be 
 tedious and I'm prone to leaving out the crucial "static" after 
 "else", so I was wondered if it was possible to write a 
 template that would resemble the switch statement, but for 
 types.

 Closest I came up to was this:

   void match(T, Fs...)() {
     foreach (F; Fs) {
       static if (isFunctionPointer!F) {
         alias Ps = Parameters!F;
         static if (Ps.length == 1) {
           static if (is(Ps[0] == T)) F(Ps[0].init);
         }
       }
     }
   }

   void test(T)(T t) {
     match!(T,
       (int _) => writeln("Matched int"),
       (string _) => writeln("Matched string")
     );
   }

 But that's pretty limited and I'd like to be able to match on 
 whether a type derives from T as well. I just can't figure it 
 out.

 Something like this would be ideal...

   match!(T,
     int => writeln("Matched int"),
     is(T : SomeObject) => writeln("Derives from SomeObject")
   );

 Anyone able to improve on it?
Instead of passing functions to match!, pass pairs of arguments, like this: match!(T, int, writeln("Matched int"), is(T : SomeObject), writeln("Derives from SomeObject"); ); Now, in the implementation, foreach pair of arguments, if the first member is a type that matches your target, perform that branch; otherwise, if the first member is a boolean value, and it is true, perform the branch.
Jun 25 2016
parent reply Lodovico Giaretta <lodovico giaretart.net> writes:
On Saturday, 25 June 2016 at 09:07:19 UTC, Lodovico Giaretta 
wrote:
 Instead of passing functions to match!, pass pairs of 
 arguments, like this:

     match!(T,
         int, writeln("Matched int"),
         is(T : SomeObject), writeln("Derives from SomeObject");
     );

 Now, in the implementation, foreach pair of arguments, if the 
 first member is a type that matches your target, perform that 
 branch; otherwise, if the first member is a boolean value, and 
 it is true, perform the branch.
Of course I meant: match!(T, int, () {writeln("Matched int");}, is(T : SomeObject), () {writeln("Derives from SomeObject");} ); You could probably even match on the actual value (instead of its type) and pass it (correctly casted) to the functions: match!(t, int, (int t) {writeln("Matched int ", t);}, is(T : SomeObject), (SomeObject t) {writeln(t, " derives from SomeObject");} ); I don't have time to implement it now, but I think it's not too difficult.
Jun 25 2016
parent reply John <johnch_atms hotmail.com> writes:
On Saturday, 25 June 2016 at 09:12:12 UTC, Lodovico Giaretta 
wrote:
 On Saturday, 25 June 2016 at 09:07:19 UTC, Lodovico Giaretta 
 wrote:
 Instead of passing functions to match!, pass pairs of 
 arguments, like this:

     match!(T,
         int, writeln("Matched int"),
         is(T : SomeObject), writeln("Derives from SomeObject");
     );

 Now, in the implementation, foreach pair of arguments, if the 
 first member is a type that matches your target, perform that 
 branch; otherwise, if the first member is a boolean value, and 
 it is true, perform the branch.
Of course I meant: match!(T, int, () {writeln("Matched int");}, is(T : SomeObject), () {writeln("Derives from SomeObject");} );
Thanks for the help, both. This appeared to work, until I realised the lambda isn't static: void match(T, cases...)() { static if (cases.length == 1) cases[0](); else static if (cases.length > 2) { static if (is(typeof(cases[0]) == bool)) { static if (cases[0]) cases[1](); else match!(T, cases[2 .. $]); } else static if (is(T == cases[0])) cases[1](); else match!(T, cases[2 .. $]); } } void test(T)(T value) { int i; string s; match!(T, int, () => i = value, string, () => s = value ); } test(1); test("A string"); The compiler complains about not being able convert an int to a string and vice versa.
Jun 25 2016
parent reply Lodovico Giaretta <lodovico giaretart.net> writes:
On Saturday, 25 June 2016 at 10:39:09 UTC, John wrote:
 Thanks for the help, both. This appeared to work, until I 
 realised the lambda isn't static:

   void match(T, cases...)() {
     static if (cases.length == 1) cases[0]();
     else static if (cases.length > 2) {
       static if (is(typeof(cases[0]) == bool)) {
         static if (cases[0]) cases[1]();
         else match!(T, cases[2 .. $]);
       }
       else static if (is(T == cases[0])) cases[1]();
       else match!(T, cases[2 .. $]);
     }
   }

   void test(T)(T value) {
     int i;
     string s;
     match!(T,
       int, () => i = value,
       string, () => s = value
     );
   }

   test(1);
   test("A string");

 The compiler complains about not being able convert an int to a 
 string and vice versa.
If you want this to work, you need your lambdas to take the casted value as a parameter: void test(T)(T value) { int i; string s; match!(value, int, (val) => i = val, string, (val) => s = val ); } And of course you need to modify match! for this to work.
Jun 25 2016
parent reply Lodovico Giaretta <lodovico giaretart.net> writes:
On Saturday, 25 June 2016 at 12:30:22 UTC, Lodovico Giaretta 
wrote:
 If you want this to work, you need your lambdas to take the 
 casted value as a parameter:

    void test(T)(T value) {
      int i;
      string s;
      match!(value,
        int, (val) => i = val,
        string, (val) => s = val
      );
    }

 And of course you need to modify match! for this to work.
Something like this: void match(alias t, cases...)() { static if (cases.length == 1) cases[0](); else static if (cases.length > 2) { static if (is(typeof(cases[0]) == bool)) { static if (cases[0]) cases[1](t); else match!(t, cases[2 .. $]); } else static if (is(typeof(t) == cases[0])) cases[1](t); else match!(t, cases[2 .. $]); } } void test(T)(T value) { int i; string s; match!(value, int, (val) => i = val, string, (val) => s = val ); } void main() { test(1); test("A string"); }
Jun 25 2016
parent John <johnch_atms hotmail.com> writes:
On Saturday, 25 June 2016 at 12:35:39 UTC, Lodovico Giaretta 
wrote:
 On Saturday, 25 June 2016 at 12:30:22 UTC, Lodovico Giaretta 
 wrote:
 If you want this to work, you need your lambdas to take the 
 casted value as a parameter:
Thanks.
Jun 25 2016
prev sibling parent reply ketmar <ketmar ketmar.no-ip.org> writes:
On Saturday, 25 June 2016 at 08:46:05 UTC, John wrote:
 Anyone able to improve on it?
q&d hack: template tyma(T, Cases...) { import std.traits; template GetFunc(size_t idx) { static if (idx >= Cases.length) { static assert(0, "no delegate for match"); } else static if (isCallable!(Cases[idx])) { enum GetFunc = Cases[idx]; } else { enum GetFunc = GetFunc!(idx+1); } } template Matcher(size_t idx) { //pragma(msg, "T=", T, "; idx=", idx, "; Cases[idx]=", Cases[idx], "; is=", is(typeof(T) == Cases[idx])); static if (idx >= Cases.length) { static assert(0, "no match, consider adding `void` branch"); } else static if (isCallable!(Cases[idx])) { enum Matcher = Matcher!(idx+1); } else static if (is(Cases[idx] == void)) { enum Matcher = GetFunc!(idx+1); } else static if (is(typeof(Cases[idx]) == string)) { mixin("static if (is(T:"~Cases[idx]~")) enum Matcher = GetFunc!(idx+1); else enum Matcher = Matcher!(idx+1);"); } else static if (is(typeof(Cases[idx]))) { static assert(0, "unexpected something in cases: "~Cases[idx].stringof); } else static if (is(T == Cases[idx])) { enum Matcher = GetFunc!(idx+1); } else { enum Matcher = Matcher!(idx+1); } } enum tyma = Matcher!0; } void main () { import std.stdio; auto res = tyma!(int, string, () => "string", "long", () => "integral", void, () => "anything", )(); writeln(res); } note that you should separate type names from labdas with "," instead of doing `int => "integral`, and have to add `()` at the end to actually call the delegate.
Jun 25 2016
parent ketmar <ketmar ketmar.no-ip.org> writes:
also, there is a subtle bug in matcher. sorry. ;-)
Jun 25 2016