Option types and pattern matching.

TheFlyingFiddle via Digitalmars-d digitalmars-d at puremagic.com
Sun Oct 25 11:15:18 PDT 2015


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.












More information about the Digitalmars-d mailing list