October 25, 2015
On Sunday, 25 October 2015 at 06:22:51 UTC, TheFlyingFiddle wrote:
> You can do something very similar to that. With slightly different syntax.
>
> import std.traits;
> import std.conv;
> import std.variant;
> struct CMatch(T...) if(T.length == 1)
> {
>    alias U = typeof(T[0]);
>    static bool match(Variant v)
>    {
>       if(auto p = v.peek!U)
>          return *p == T[0];
>       return false;
>    }
> }
>
> auto ref match(Handlers...)(Variant v)
> {
>    foreach(handler; Handlers)
>    {
>       alias P = Parameters!handler;
>       static if(P.length == 1)
>       {
>          static if(isInstanceOf!(CMatch, P[0]))
>          {
> 	    if(P[0].match(v))
>                return handler(P[0].init);
>          }
>          else
>          {
>             if(auto p = v.peek!(P[0]))
>                return handler(*p);
>          }
>       }
>       else
>       {
>          return handler();
>       }
>    }
>
>    assert(false, "No matching pattern");
> }
>
> unittest
> {
>     Variant v = 5;
>     string s = v.match!(
> 	(CMatch!7) => "Lucky number seven",
> 	(int n)    => "Not a lucky number: " ~ n.to!string,
> 	()         => "No value found!");
>
>    writeln(s);
> }

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.
October 25, 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.










October 25, 2015
On 25-Oct-2015 08:01, Nerve wrote:
> Hello D community! First time poster, I'm utterly fascinated with this
> language's mix of features. It's powerful and expressive.
>
> There are just two conveniences I'd like to see out of D. The first is
> pattern matching, a functional construct which can unwrap tuples or
> other containers, usually evaluates to a value (in most languages), and
> which almost always idiomatically enforces the programmer to match over
> every possible case of a given type.
>
> While ML-inspired languages like F# and OCaml have some good pattern
> matching syntax, it's not syntax which would fit in to D; I suggest
> taking inspiration of Rusts's matching construct.
>
> match x {
>      Some(7) => "Lucky number 7!",
>      Some(_) => "No lucky number.",
>      None => "No value found"
> }
>

I humbly believe that D may just add special re-write rule to the switch statement in order to allow user-defined switchable types. This goes along nicely with the trend - e.g. foreach statement works with anything having static range interfaces or opApply.

All in all we've seen a lot of examples of how it's done in the library but always somewhat cumbersome. The next big problem would be that switch is a statement not expression which limits use-cases of user-defined pattern matching.


-- 
Dmitry Olshansky
October 25, 2015
On Sunday, 25 October 2015 at 18:23:42 UTC, Dmitry Olshansky wrote:
> I humbly believe that D may just add special re-write rule to the switch statement in order to allow user-defined switchable types. This goes along nicely with the trend - e.g. foreach statement works with anything having static range interfaces or opApply.

I don't think I understand this, could you elaborate?
October 25, 2015
On Sunday, 25 October 2015 at 18:15:20 UTC, TheFlyingFiddle wrote:
> 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.

Without having looked at this in detail, phobos should have a good generic implementation of pattern matching, so you should consider creating a pull request (std.functional would be the natural home I think).
October 25, 2015
On 2015-10-25 20:00, John Colvin wrote:

> Without having looked at this in detail, phobos should have a good
> generic implementation of pattern matching, so you should consider
> creating a pull request (std.functional would be the natural home I think).

I've been waiting for this PR [1] to get merged before implementing pattern matching as a library function.

[1] https://github.com/D-Programming-Language/dmd/pull/5201

-- 
/Jacob Carlborg
October 26, 2015
On 2015-10-25 19:23, Dmitry Olshansky wrote:

> I humbly believe that D may just add special re-write rule to the switch
> statement in order to allow user-defined switchable types. This goes
> along nicely with the trend - e.g. foreach statement works with anything
> having static range interfaces or opApply.

Do you think that could handle all different type of patterns? For example extractor patterns:

match x {
    case Foo(bar) => println(bar)
}

The above is Scala.

-- 
/Jacob Carlborg
October 26, 2015
For Option see https://w0rp.com/project/dstruct/dstruct/option/
October 26, 2015
On Sunday, 25 October 2015 at 06:22:51 UTC, TheFlyingFiddle wrote:
> On Sunday, 25 October 2015 at 05:45:15 UTC, Nerve wrote:
>> On Sunday, 25 October 2015 at 05:05:47 UTC, Rikki Cattermole wrote:
>>> Since I have no idea what the difference between Some(_), None and default. I'll assume it's already doable.
>>
>> _ represents all existing values not matched. In this case, Some(_) represents any integer value that is not 7. None specifically matches the case where no value has been returned. We are, in most languages, also able to unwrap the value:
>>
>> match x {
>>     Some(7) => "Lucky number 7!",
>>     Some(n) => "Not a lucky number: " ~ n,
>>     None => "No value found"
>> }
>
> You can do something very similar to that. With slightly different syntax.
>
> import std.traits;
> import std.conv;
> import std.variant;
> struct CMatch(T...) if(T.length == 1)
> {
>    alias U = typeof(T[0]);
>    static bool match(Variant v)
>    {
>       if(auto p = v.peek!U)
>          return *p == T[0];
>       return false;
>    }
> }
>
> auto ref match(Handlers...)(Variant v)
> {
>    foreach(handler; Handlers)
>    {
>       alias P = Parameters!handler;
>       static if(P.length == 1)
>       {
>          static if(isInstanceOf!(CMatch, P[0]))
>          {
> 	    if(P[0].match(v))
>                return handler(P[0].init);
>          }
>          else
>          {
>             if(auto p = v.peek!(P[0]))
>                return handler(*p);
>          }
>       }
>       else
>       {
>          return handler();
>       }
>    }
>
>    assert(false, "No matching pattern");
> }
>
> unittest
> {
>     Variant v = 5;
>     string s = v.match!(
> 	(CMatch!7) => "Lucky number seven",
> 	(int n)    => "Not a lucky number: " ~ n.to!string,
> 	()         => "No value found!");
>
>    writeln(s);
> }

You could also emulate constant matching using default parameters (albeit with the restriction that they must be after any non-default/constant parameters), since the defaults form part of the function's type. I tried making something like this earlier this summer and it'd check that a given value was first equal to the default parameter and match if so, or match if there was no default parameter but the types matched.

e.g.
//template ma(tch/g)ic

unittest
{
    Algebraic!(string, int, double, MyStruct) v = 5;
    string s = v.match!(
        (string s = "") => "Empty string!",
        (string s) => s,
        (int i = 7) => "Lucky number 7",
        (int i = 0) => "Nil",
        (int i) => i.to!string,
        (double d) => d.to!string,
        (MyStruct m = MyStruct(15)) => "Special MyStruct value",
        (MyStruct m) => m.name, //
        () => "ooer");
    writeln(s);
}

It's a bit ugly overloading language features like this, but it makes the syntax a little prettier.

I'd really like to see proper pattern matching as a language-level feature however; for all the emulating it we can do in D, it's not very pretty or friendly and optimising it is harder since the language has no concept of pattern matching. Things like Option (and other ADTs) are lovely, but really need good pattern matching to become worthwhile IMO (e.g. Java Optional<T> has a get() method that throws on empty, which undermines the main reason to use optional - to have a guarantee that you handle the empty case gracefully; Scala's Option is really nice on the other hand since you can/should pattern match).
October 26, 2015
On 2015-10-26 12:40, Edmund Smith wrote:

> You could also emulate constant matching using default parameters
> (albeit with the restriction that they must be after any
> non-default/constant parameters), since the defaults form part of the
> function's type. I tried making something like this earlier this summer
> and it'd check that a given value was first equal to the default
> parameter and match if so, or match if there was no default parameter
> but the types matched.
>
> e.g.
> //template ma(tch/g)ic
>
> unittest
> {
>      Algebraic!(string, int, double, MyStruct) v = 5;
>      string s = v.match!(
>          (string s = "") => "Empty string!",
>          (string s) => s,
>          (int i = 7) => "Lucky number 7",
>          (int i = 0) => "Nil",
>          (int i) => i.to!string,
>          (double d) => d.to!string,
>          (MyStruct m = MyStruct(15)) => "Special MyStruct value",
>          (MyStruct m) => m.name, //
>          () => "ooer");
>      writeln(s);
> }
>
> It's a bit ugly overloading language features like this, but it makes
> the syntax a little prettier.

Why not just use a value as an extra argument:

v.match!(
    7, (int i) => "Lucky number 7"
);

> Scala's Option is really nice on the other hand since you can/should pattern match).

I thought it was preferred to use the higher order functions like "map" and "filter".

-- 
/Jacob Carlborg