On Sunday, 25 October 2015 at 14:43:25 UTC, Nerve wrote:
On Sunday, 25 October 2015 at 06:22:51 UTC, TheFlyingFiddle wrote: That is actually freaking incredible. It evaluates to a value, unwraps values, matches against the None case...I guess the only thing it doesn't do is have compiler-enforced matching on all cases. Unless I'm just slow this morning and not thinking of other features a pattern match should have.

With some changes to the match function one could enforce that a default handler is always present so that all cases are handled or error on compilation if it's not.

Something like: (naive way)

auto ref match(Handlers...)(Variant v)
{
    //Default handler must be present and be the last handler.
    static assert(Parameters!(Handlers[$ - 1]).length == 0,
                  "Matches must have a default handler.");
}

now

//Would be a compiler error.
v.match!((int n) => n.to!string));

//Would work.
v.match!((int n) => n.to!string),
         () => "empty");

Additionally one could check that all return types share a common implicit conversion type. And cast to that type in the match.

//Returns would be converted to long before being returned.
v.match!((int n)  => n, //Returns int
         (long n) => n, //Returns long
         ()       => 0);

Or if they don't share a common implicit conversion type return a Variant result.

Also the handlers could be sorted so that the more general handlers are tested later.

//Currently
v.match!((int n) => n,
         (CMatch!7) => 0,
         () => 0);

Would not really work since (int n) is tested for first so CMatch!7 would never get called even if the value was 7. But if we sort the incoming Handlers with CMatch instances at the front then the above would work as a user intended. This would also allow the empty/default case to be in any order.

For even more error checking one could make sure that no CMatch value intersects with another. That way if there are for example two cases with CMatch!7 then an assert error would be emited.

So:
v.match!((CMatch!7) => "The value 7",
         (CMatch!7) => "A seven value",
         ()         => "empty");

Would error with something like "duplicate value in match"

Other extensions one could do to the pattern matching is:

1. Allow more then one value in CMatch. So CMatch!(5, 7) would mean either 5 or 7. 2. Rust has a range syntax, this could be kind of nice. Maybe RMatch!(1, 10) for that.
3. Add a predicate match that takes a lambda.

//Predicate match.
struct PMatch(alias lambda)
{
    alias T = Parameters!(lambda)[0];
    alias this value;
    T value;
    static bool match(Variant v)
    {
       alias P = Parameters!lambda;
       if(auto p = v.peek!P)
       {
          if(lambda(*p))
          {
              value = *p;
              return true;
          }
       }
       return false;
    }
}

struct RMatch(T...) if(T.length == 2)
{
   alias C = CommonType!(typeof(T[0]), typeof(T[1]));
   C value;
   alias this value;

   static bool match(Variant v)
   {
      if(auto p = v.peek!C)
      {
         if(*p >= T[0] && *p < T[1])
         {
             value = *p;
             return true;
         }
      }
      return false;
   }
}

v.match!(
      (RMatch!(1, 10) n) => "Was (1 .. 10): " ~ n.to!string;
(PMatch!((int x) => x % 2 == 0) n) => "Was even: " ~ n.to!string, (PMatch!((int x) => x % 2 == 1) n) => "Was odd: " ~ n.to!string,
      () => "not an integer");

The PMatch syntax is not the most fun... It can be reduced slightly if your not using a variant but a Maybe!T type or a regular old type to.

The pattern matching can have more static checks and the syntax can look a somewhat better if we are matching on a Maybe!T type or a regular type instead of a variant. We could for example make sure that all CMatch/RMatch values have the correct type and (in some limited cases) ensure that all cases are covered without the need for a default switch.

All in all I think that something like this would be a fairly comprehensive library pattern matching solution. Catching many types of programming errors at compile-time. It could be fast as well if all the constants and ranges are converted into a switch statements (via string mixin magic).

This problem has gained my interest and I plan on implementing this sometime this week. I'll post a link to the source when it's done if anyone is interested in it.










Reply via email to