Jump to page: 1 2 3
Thread overview
Option types and pattern matching.
Oct 25, 2015
Nerve
Oct 25, 2015
Rikki Cattermole
Oct 25, 2015
Nerve
Oct 25, 2015
Rikki Cattermole
Oct 25, 2015
Nerve
Oct 25, 2015
Rikki Cattermole
Oct 25, 2015
Rikki Cattermole
Oct 25, 2015
TheFlyingFiddle
Oct 25, 2015
Nerve
Oct 25, 2015
TheFlyingFiddle
Oct 25, 2015
John Colvin
Oct 25, 2015
Jacob Carlborg
Oct 26, 2015
Edmund Smith
Oct 26, 2015
Jacob Carlborg
Oct 26, 2015
TheFlyingFiddle
Oct 26, 2015
Edmund Smith
Oct 26, 2015
TheFlyingFiddle
Oct 27, 2015
Kagamin
Oct 27, 2015
TheFlyingFiddle
Oct 27, 2015
Kagamin
Oct 27, 2015
Meta
Oct 27, 2015
TheFlyingFiddle
Oct 25, 2015
cym13
Oct 25, 2015
Jacob Carlborg
Oct 25, 2015
Dmitry Olshansky
Oct 25, 2015
TheFlyingFiddle
Oct 26, 2015
Jacob Carlborg
Oct 26, 2015
Dmitry Olshansky
Oct 26, 2015
Kagamin
October 25, 2015
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"
}

From that bit of code, we can see another feature at work: The Option type. It wraps another type (i.e. Option int, Option Car) and represents a wrapped presence of a value of that type (Some(n), Some(aCar)) or an absence of that type (None).

Combined with pattern matching, we end up with a safe, functional construct which can replace a switch statement in most cases, returns a value, is incredibly compact and readable, and can be used with Options to ensure that we always account for the possibility of a value not present, eliminating a whole class of errors when we use it judiciously.

My only concern is that switch statements, while horrendous syntactically, are extremely performant and essentially compile to a series of branches.

Are there any counter-arguments for the implementation of these two features? Is D in a state where language additions have come to a stop?
October 25, 2015
On 25/10/15 6:01 PM, 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"
> }
>
>  From that bit of code, we can see another feature at work: The Option
> type. It wraps another type (i.e. Option int, Option Car) and represents
> a wrapped presence of a value of that type (Some(n), Some(aCar)) or an
> absence of that type (None).

Option = Varient

> Combined with pattern matching, we end up with a safe, functional
> construct which can replace a switch statement in most cases, returns a
> value, is incredibly compact and readable, and can be used with Options
> to ensure that we always account for the possibility of a value not
> present, eliminating a whole class of errors when we use it judiciously.
>
> My only concern is that switch statements, while horrendous
> syntactically, are extremely performant and essentially compile to a
> series of branches.
>
> Are there any counter-arguments for the implementation of these two
> features? Is D in a state where language additions have come to a stop?

So basically a little bit of magic for matching if having a value or not and switch statement.

Since I have no idea what the difference between Some(_), None and default. I'll assume it's already doable.
October 25, 2015
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"
}

Or something to that effect. The equivalent switch statement right now would be:

if (x.hasValue()) {
    switch (*x.peek!(int)) {
        case 7:    writeln("Lucky number seven!"); break;
        default:   writeln("Not a lucky number: ", *x.peek!(int)); break;
    }
} else {
    writeln("No value.");
}

This does not return a value (is a procedural structure); the switch cannot match null; in order to unwrap, we must call peek() again; and between the extra if-else and the break statements, this is not as clean.

As a note, pattern matching could almost be considered an extended form of the ?: operator, which matches over value cases rather than boolean truthiness.

Apologies if this is all below you, I'm not in Andrei's or Walter's league, just an interested party trying to make suggestions to better the language.
October 25, 2015
On 25/10/15 6:45 PM, 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"
> }
>
> Or something to that effect. The equivalent switch statement right now
> would be:
>
> if (x.hasValue()) {
>      switch (*x.peek!(int)) {
>          case 7:    writeln("Lucky number seven!"); break;
>          default:   writeln("Not a lucky number: ", *x.peek!(int)); break;
>      }
> } else {
>      writeln("No value.");
> }

I'm pretty sure e.g. opEquals/opCmp should work here.
Shouldn't need to switch upon a primitive type. Theoretically could do it on a e.g. struct. Which has the special comparison that you want.

> This does not return a value (is a procedural structure); the switch
> cannot match null; in order to unwrap, we must call peek() again; and
> between the extra if-else and the break statements, this is not as clean.
>
> As a note, pattern matching could almost be considered an extended form
> of the ?: operator, which matches over value cases rather than boolean
> truthiness.
>
> Apologies if this is all below you, I'm not in Andrei's or Walter's
> league, just an interested party trying to make suggestions to better
> the language.

No no it's fine. Only this morning I was toying with the idea of variable length struct's on IRC. Turns out, wouldn't really work.
October 25, 2015
On Sunday, 25 October 2015 at 05:53:32 UTC, Rikki Cattermole wrote:
> I'm pretty sure e.g. opEquals/opCmp should work here.
> Shouldn't need to switch upon a primitive type. Theoretically could do it on a e.g. struct. Which has the special comparison that you want.

Hm...these are boolean operators. This means we can only compare two cases at a time, does it not? Negates the strength of a switch/pattern match, unless there's something I'm missing.

What are these variable length structs you mention, and their special comparisons? How would we use them?


October 25, 2015
On 25/10/15 7:05 PM, Nerve wrote:
> On Sunday, 25 October 2015 at 05:53:32 UTC, Rikki Cattermole wrote:
>> I'm pretty sure e.g. opEquals/opCmp should work here.
>> Shouldn't need to switch upon a primitive type. Theoretically could do
>> it on a e.g. struct. Which has the special comparison that you want.
>
> Hm...these are boolean operators. This means we can only compare two
> cases at a time, does it not? Negates the strength of a switch/pattern
> match, unless there's something I'm missing.

Well you only need to compare two cases.
I need to spend a bit of time, to see if what I think can be done, is actually possible. Essentially toying with your 'Some' types comparison rules.

> What are these variable length structs you mention, and their special
> comparisons? How would we use them?

Oh the idea was a complete flop. It's just an example of how welcoming ideas are. Just no guarantee they'll make it to even a consideration from Walter.

October 25, 2015
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);
}

October 25, 2015
On 25/10/15 7:15 PM, Rikki Cattermole wrote:
> On 25/10/15 7:05 PM, Nerve wrote:
>> On Sunday, 25 October 2015 at 05:53:32 UTC, Rikki Cattermole wrote:
>>> I'm pretty sure e.g. opEquals/opCmp should work here.
>>> Shouldn't need to switch upon a primitive type. Theoretically could do
>>> it on a e.g. struct. Which has the special comparison that you want.
>>
>> Hm...these are boolean operators. This means we can only compare two
>> cases at a time, does it not? Negates the strength of a switch/pattern
>> match, unless there's something I'm missing.
>
> Well you only need to compare two cases.
> I need to spend a bit of time, to see if what I think can be done, is
> actually possible. Essentially toying with your 'Some' types comparison
> rules.
>
>> What are these variable length structs you mention, and their special
>> comparisons? How would we use them?
>
> Oh the idea was a complete flop. It's just an example of how welcoming
> ideas are. Just no guarantee they'll make it to even a consideration
> from Walter.

Just alter the value in v1 under the main function, to see the different behaviors.

It's slightly backwards, you expect what is default to be a case. I'm sure you can handle changing the logic of opEquals to be what you want.

Also to get a version of Foo that has haveVal as false, probably should be a static method, instead of that custom usage. Not to mention templating it ext. ext.

struct Foo {
        int val;
        bool haveVal = true;

        alias val this;

        bool opEquals(Foo f) {
                if (haveVal == f.haveVal) {
                        if (haveVal)
                                return val == f.val;
                        else
                                return true;
                } else
                        return false;
        }
}

void main() {
        import std.stdio : writeln;
        Foo v1 = Foo(0, false);

        switch(v1) {
                case 8:
                        writeln(9);
                        break;
                case 6:
                        writeln(6);
                        break;
                case Foo(0, false):
                        writeln("no value");
                        break;
                default:
                        writeln("unknown: ", v1.val);
                        break;
        }
}

October 25, 2015
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"
> }
>
> Or something to that effect. The equivalent switch statement right now would be:
>
> if (x.hasValue()) {
>     switch (*x.peek!(int)) {
>         case 7:    writeln("Lucky number seven!"); break;
>         default:   writeln("Not a lucky number: ", *x.peek!(int)); break;
>     }
> } else {
>     writeln("No value.");
> }
>
> This does not return a value (is a procedural structure); the switch cannot match null; in order to unwrap, we must call peek() again; and between the extra if-else and the break statements, this is not as clean.
>
> As a note, pattern matching could almost be considered an extended form of the ?: operator, which matches over value cases rather than boolean truthiness.
>
> Apologies if this is all below you, I'm not in Andrei's or Walter's league, just an interested party trying to make suggestions to better the language.

Although it doesn't exactly fit the problem at hand I'd like to mention
predSwitch. For most cases it does what you could expect from pattern
matching I think. Here is an example showing its strength and limits on your
showcase:

    import std.conv;
    import std.stdio;
    import std.algorithm.comparison;
    import std.variant;


    void main(string[] args) {
        Variant x;

        x = 42;

        if (x.hasValue) {
            x.predSwitch!((a,b) => *a.peek!int == b) (
                7,  "Lucky number!",
                42, "This should be a lucky number too!",
                "No luck, the number was " ~ x.to!string
            ).writeln;
        }
        else {
            writeln("No value");
        }
    }
October 25, 2015
On 2015-10-25 06: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"
> }
>
>  From that bit of code, we can see another feature at work: The Option
> type. It wraps another type (i.e. Option int, Option Car) and represents
> a wrapped presence of a value of that type (Some(n), Some(aCar)) or an
> absence of that type (None).
>
> Combined with pattern matching, we end up with a safe, functional
> construct which can replace a switch statement in most cases, returns a
> value, is incredibly compact and readable, and can be used with Options
> to ensure that we always account for the possibility of a value not
> present, eliminating a whole class of errors when we use it judiciously.
>
> My only concern is that switch statements, while horrendous
> syntactically, are extremely performant and essentially compile to a
> series of branches.
>
> Are there any counter-arguments for the implementation of these two
> features? Is D in a state where language additions have come to a stop?

Both of these can be implemented in library code. Although the syntax won't be as nice.

I wouldn't mind having pattern matching as a language feature.

-- 
/Jacob Carlborg
« First   ‹ Prev
1 2 3