Thread overview
static switch/pattern matching
Jun 25, 2016
John
Jun 25, 2016
Lodovico Giaretta
Jun 25, 2016
Lodovico Giaretta
Jun 25, 2016
John
Jun 25, 2016
Lodovico Giaretta
Jun 25, 2016
Lodovico Giaretta
Jun 25, 2016
John
Jun 25, 2016
ketmar
Jun 25, 2016
ketmar
June 25, 2016
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?
June 25, 2016
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.
June 25, 2016
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.
June 25, 2016
also, there is a subtle bug in matcher. sorry. ;-)
June 25, 2016
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.
June 25, 2016
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.
June 25, 2016
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.
June 25, 2016
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");
   }
June 25, 2016
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.