www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Option types and pattern matching.

reply Nerve <nervecenter7 gmail.com> writes:
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.


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?
Oct 24 2015
next sibling parent reply Rikki Cattermole <alphaglosined gmail.com> writes:
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.


 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.
Oct 24 2015
parent reply Nerve <nervecenter7 gmail.com> writes:
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.
Oct 24 2015
next sibling parent reply Rikki Cattermole <alphaglosined gmail.com> writes:
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.
Oct 24 2015
parent reply Nerve <nervecenter7 gmail.com> writes:
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?
Oct 24 2015
parent reply Rikki Cattermole <alphaglosined gmail.com> writes:
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.
Oct 24 2015
parent Rikki Cattermole <alphaglosined gmail.com> writes:
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; } }
Oct 24 2015
prev sibling next sibling parent reply TheFlyingFiddle <kurtyan student.chalmers.se> writes:
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); }
Oct 24 2015
next sibling parent reply Nerve <nervecenter7 gmail.com> writes:
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.
Oct 25 2015
parent reply TheFlyingFiddle <kurtyan student.chalmers.se> writes:
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.
Oct 25 2015
parent reply John Colvin <john.loughran.colvin gmail.com> writes:
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).
Oct 25 2015
parent Jacob Carlborg <doob me.com> writes:
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
Oct 25 2015
prev sibling parent reply Edmund Smith <edmund.pd.smith gmail.com> writes:
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).
Oct 26 2015
next sibling parent Jacob Carlborg <doob me.com> writes:
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
Oct 26 2015
prev sibling parent reply TheFlyingFiddle <kurtyan student.chalmers.se> writes:
On Monday, 26 October 2015 at 11:40:09 UTC, Edmund Smith wrote:
 On Sunday, 25 October 2015 at 06:22:51 UTC, TheFlyingFiddle 
 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.
This does look nicer indeed.
Why not just use a value as an extra argument:
v.match!(
    7, (int i) => "Lucky number 7"
);
I like this you could go further with this to allow any number of constants. v.match!( 5, 7, i => "Was: " ~ i.to!string, (int i) => "Was this: " ~ i.to!string); Or for ranges. v.match!( MatchR!(1, 10), i => "Was: " ~ i.to!string, //Matches 1 .. 10 (int i) => "Was this: " ~ i.to!string);
 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.
One could probably get something like this: int i = 5; string s = i.match!( 5, 7, n => "Five or seven", MatchR!(10, 100), n => "Was between ten and a hundred", (n) => "Was: " ~ n.to!string); to fold into something like this: void match(T...)(int i) { switch(i) { case 5: case 7: return (T[2])!int(i); case 10: .. case 99: return (T[3])!int(i); default: return (T[4])!int(i); } } int i = 5; string s = match!(/* lambdas and whatnot */), i); With some template/ctfe and string mixings magic. In-lining, constant folding etc could probably just reduce it to the equvalent of: int i = 5; string s = "Five or seven"; (if there is really good constant folding :P) It might however generate lot's of useless symbols in the resulting code making code size's larger.
 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 -
Another thing that has always bothered me with Optional<T> in Java in addition to this is that the optional value itself might be null. So to write robust code you first have to check against null on the option value :P.
 Scala's Option is really nice on the other hand since you 
 can/should pattern match).
Don't really see a point in an optional type if can access the underlying value without first checking if it's there.
Oct 26 2015
parent reply Edmund Smith <edmund.pd.smith gmail.com> writes:
On Monday, 26 October 2015 at 14:13:20 UTC, TheFlyingFiddle wrote:
 On Monday, 26 October 2015 at 11:40:09 UTC, Edmund Smith wrote:
 Scala's Option is really nice on the other hand since you 
 can/should pattern match).
Don't really see a point in an optional type if can access the underlying value without first checking if it's there.
The key difference with (exhaustive) pattern matching is that it *is* the check that the value is there. Pattern matching enforces the existence of an on-nothing clause for Optional, on-error for Error, on-Leaf and on-Branch for Bintrees, etc. And even with nice higher-order functions, plain pattern matching is quite valuable for finely controlled error/resource handling, and I often see it in Rust code as well as Scala (and I've seen it used in Haskell occasionally too). A brief, contrived example use-case: //External code that disallows monadic int[] void processThatMustOccur(int[] data); ... Option!File findFile(string fname); Result!(int[]) parseInts(File file); //verbose for clarity void parseMatches(string path) { Option!File ofile = path.findFile(); //Static guarantee of handling value not present ofile match { None() => { //Handle error for no file found, retry with new path } //The existence of file is effectively proof that ofile is present Some(file) => { Option!(int[]) odata = file.parseInts(); odata match { Success(data) => processThatMustOccur(preProcess(data)); Error(error) => //Handle error for bad parse, backtrack depends on error } } } //Continue after processing data } void parseMonadic(string path) { path.findFile() .bind!parseInts() .bind!(map!preProcess) .bind!processThatMustOccur() .onFailure!backTrack //How can we backtrack a variable amount easily? //Continue after processing data } The error control loss can be mostly avoided by using an appropriate error monad or API design, but there's still the issue of interfacing with non-monadic code. It essentially provides a guarantee that the equivalent of 'T get();' will handle errors, like having a checked exception version 'T get() throws OnNotPresent;' instead. It also scales up much better than having these checked exceptions on not-present ADT accesses.
Oct 26 2015
parent reply TheFlyingFiddle <kurtyan student.chalmers.se> writes:
On Monday, 26 October 2015 at 15:58:38 UTC, Edmund Smith wrote:
 On Monday, 26 October 2015 at 14:13:20 UTC, TheFlyingFiddle 
 wrote:
 On Monday, 26 October 2015 at 11:40:09 UTC, Edmund Smith wrote:
 Scala's Option is really nice on the other hand since you 
 can/should pattern match).
Don't really see a point in an optional type if can access the underlying value without first checking if it's there.
The key difference with (exhaustive) pattern matching is that it *is* the check that the value is there. Pattern matching enforces the existence of an on-nothing clause for Optional, on-error for Error, on-Leaf and on-Branch for Bintrees, etc. And even with nice higher-order functions, plain pattern matching is quite valuable for finely controlled error/resource handling, and I often see it in Rust code as well as Scala (and I've seen it used in Haskell occasionally too). A brief, contrived example use-case:
What I meant is that I don't really see the point in optionals that look something like this: struct Optional(T) { T value; bool empty; ref T get() { enforce(!empty, "Value not present!"); return value; } //Stuff... } Optional!(int[]) doSomething(...); void process(int[]); void foo() { Optional!(int[]) result = doSomething(...); process(result.get()); } I mean sure you get a null check in foo instead of in process but this style of writing code does not really give you much of an advantage since you can't really handle the errors much better then you could a Null exception. If you instead use pattern matching as in your example you have much better context information that can actually help you do something in the case a value is not there.
Oct 26 2015
parent reply Kagamin <spam here.lot> writes:
On Monday, 26 October 2015 at 16:42:27 UTC, TheFlyingFiddle wrote:
 If you instead use pattern matching as in your example you have 
 much better context information that can actually help you do 
 something in the case a value is not there.
Probably possible: Some!T get(T)(Option!T item) { Some!T r; //Static guarantee of handling value not present item match { None() => { throw new Exception("empty!"); } Some(t) => { r=t; } } return r; } Then: Option!File file; Some!File s = file.get();
Oct 27 2015
parent reply TheFlyingFiddle <kurtyan student.chalmers.se> writes:
On Tuesday, 27 October 2015 at 07:55:46 UTC, Kagamin wrote:
 On Monday, 26 October 2015 at 16:42:27 UTC, TheFlyingFiddle 
 wrote:
 If you instead use pattern matching as in your example you 
 have much better context information that can actually help 
 you do something in the case a value is not there.
Probably possible: Some!T get(T)(Option!T item) { Some!T r; //Static guarantee of handling value not present item match { None() => { throw new Exception("empty!"); } Some(t) => { r=t; } } return r; } Then: Option!File file; Some!File s = file.get();
Sure that would work but i don't see that it's different then an enfore since you don't have access the context where get is invoked so you can't really do anything with it. Contrived Example: void foo() { Option!File worldFile = getAFile("world.json"); auto world = parseJSON(worldFile.get()); Option!File mapFile = getAFile(world["map"]); auto map = parseJSON(mapFile.get()); //Other stuff. } Let's say we get an NoObjectException, this tells us that either the world.json file did not exist or the map file does not exist. get does not have access to that context so we wouldn't be able to tell. This next example would fix this. void foo() { Option!File worldFile = getAFile("world.json"); enforce(worldFile.hasValue, "Error while loading file: world.json"); auto world = parseJSON(worldFile.get()); Option!File mapFile = getAFile(world["map"]); enforce(mapFile.hasValue, "Error while loading file: " ~ world["map"]); auto map = parseJSON(mapFile.get()); //Other stuff } Now we know which file failed to load. But we bypassed the NoObjectException to do it. I would prefer this style instead. void foo() { Option!File worldFile = getAFile("world.json"); match worldFile { Some(value) => { auto world = parseJSON(value); Option!File mapFile = getAFile(world["map"]); match mapFile { Some(mapf) => { auto map = parseJSON(mapf); //Do something here. }, None => enforce(false, "Failed to load: " ~ world["map"]); } }, None => enforce(false, "Failed to load: world.json"); } } The reason that I prefer that is not that I like the syntax really. It's just that if the only way to get a value is to pattern match on it then you are forced to consider the case where the value was not there. Guess a D version without language support would look something like: void foo() { auto worldFile = getAFile("world.json"); worldFile.match!( (File worldf) { auto world = parseJSON(value); auto mapFile = getAFile(world["map"]); mapFile.match!( (File mapf) { auto map = parseJSON(mapf); //Do stuff; }, (None) => enforce(false, "Failed to load: " ~ world["map"]); }, (None) => enforce(false, "Failed to load: world.json") ); } The example here is very contrived. Here we just throw exceptions if the file could not load and if that is all we do we should just wrap getAFile instead but i hope you get my point.
Oct 27 2015
next sibling parent Kagamin <spam here.lot> writes:
On Tuesday, 27 October 2015 at 15:06:07 UTC, TheFlyingFiddle 
wrote:
 The reason that I prefer that is not that I like the syntax 
 really. It's just that if the only way to get a value is to 
 pattern match on it then you are forced to consider the case 
 where the value was not there.
If pattern matching is the only way, the get function above will still work: it uses only pattern matching, nothing else.
Oct 27 2015
prev sibling parent reply Meta <jared771 gmail.com> writes:
On Tuesday, 27 October 2015 at 15:06:07 UTC, TheFlyingFiddle 
wrote:
 I would prefer this style instead.
 void foo()
 {
   Option!File worldFile = getAFile("world.json");
   match worldFile {
      Some(value) => {
          auto world          = parseJSON(value);
          Option!File mapFile = getAFile(world["map"]);
          match mapFile {
             Some(mapf) => {
                auto map = parseJSON(mapf);
                //Do something here.
             },
             None => enforce(false, "Failed to load: " ~ 
 world["map"]);
          }
      },
      None => enforce(false, "Failed to load: world.json");
    }
 }
This can arguably already be done cleaner in D. if (auto worldFile = getAFile("world.json")) { auto world = parseJSON(worldFile); if (auto mapFile = getAFile(world["map")) { //... } else enforce(false, "Failed to load: " ~ world["map"]); } else enforce(false, "Failed to load: world.json"); Or even: auto worldFile = enforce(getAFile("world.json"), "Failed to load: world.json"); auto world = parseJSON(worldFile); auto mapFile = enforce(getAFile(world["map"]), "Failed to load: " ~ world["map"]); //... From what I've seen in the Rust community, they try to avoid using match as it's very syntactically heavy. They have all kinds of idioms to avoid doing matches on Option, such as the try! macro, unwrap_or, unwrap_or_else, etc. That being said, pattern matching has been one of my most-wanted D features for years.
Oct 27 2015
parent TheFlyingFiddle <kurtyan student.chalmers.se> writes:
On Tuesday, 27 October 2015 at 17:48:04 UTC, Meta wrote:
 On Tuesday, 27 October 2015 at 15:06:07 UTC, TheFlyingFiddle 
 wrote:
 This can arguably already be done cleaner in D.

 if (auto worldFile = getAFile("world.json"))
 {
     auto world = parseJSON(worldFile);
     if (auto mapFile = getAFile(world["map"))
     {
         //...
     }
     else enforce(false, "Failed to load: " ~ world["map"]);
 }
 else enforce(false, "Failed to load: world.json");

 Or even:

 auto worldFile = enforce(getAFile("world.json"), "Failed to 
 load: world.json");
 auto world = parseJSON(worldFile);
 auto mapFile = enforce(getAFile(world["map"]), "Failed to load: 
 " ~ world["map"]);
 //...

 From what I've seen in the Rust community, they try to avoid 
 using match as it's very syntactically heavy. They have all 
 kinds of idioms to avoid doing matches on Option, such as the 
 try! macro, unwrap_or, unwrap_or_else, etc.

 That being said, pattern matching has been one of my 
 most-wanted D features for years.
Yes this is much cleaner. But it does not really force a user to consider the empty case. I mean this would still compile. auto worldFile = getAFile("world.json"); auto world = parseJSON(worldFile); auto mapFile = getAFile(world["map"]); auto map = parseJSON(mapFile); What I was after was a way to at compile time ensure that all accesses to the value in the optional type are considered. From my uses of Maybe in haskell i know this get's annoying quick so I dunno if it's a good thing. But atleast you would know that in all places that optionals are accessed a handler for the empty case would be present.
Oct 27 2015
prev sibling parent cym13 <cpicard openmailbox.org> writes:
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"); } }
Oct 24 2015
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
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.


 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
Oct 25 2015
prev sibling next sibling parent reply Dmitry Olshansky <dmitry.olsh gmail.com> writes:
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.


 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
Oct 25 2015
next sibling parent TheFlyingFiddle <kurtyan student.chalmers.se> writes:
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?
Oct 25 2015
prev sibling parent reply Jacob Carlborg <doob me.com> writes:
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
Oct 26 2015
parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
On 26-Oct-2015 12:16, Jacob Carlborg wrote:
 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.
Scala does it with a bit of re-writes and partially hard-wired logic (mostly to optimize). I don't see a problem with it and D would be better off with something similar. Current switch is both too lax (accepts variables) and too rigid (only integers, string and soon(?) pointers). -- Dmitry Olshansky
Oct 26 2015
prev sibling parent Kagamin <spam here.lot> writes:
For Option see https://w0rp.com/project/dstruct/dstruct/option/
Oct 26 2015