digitalmars.D - Option types and pattern matching.
- Nerve (32/32) Oct 24 2015 Hello D community! First time poster, I'm utterly fascinated with
- Rikki Cattermole (6/35) Oct 24 2015 So basically a little bit of magic for matching if having a value or not...
- Nerve (32/34) Oct 24 2015 _ represents all existing values not matched. In this case,
- Rikki Cattermole (6/37) Oct 24 2015 I'm pretty sure e.g. opEquals/opCmp should work here.
- Nerve (7/11) Oct 24 2015 Hm...these are boolean operators. This means we can only compare
- Rikki Cattermole (8/17) Oct 24 2015 Well you only need to compare two cases.
- Rikki Cattermole (40/58) Oct 24 2015 Just alter the value in v1 under the main function, to see the different...
- TheFlyingFiddle (50/63) Oct 24 2015 You can do something very similar to that. With slightly
- Nerve (6/55) Oct 25 2015 That is actually freaking incredible. It evaluates to a value,
- TheFlyingFiddle (112/119) Oct 25 2015 With some changes to the match function one could enforce that a
- John Colvin (5/8) Oct 25 2015 Without having looked at this in detail, phobos should have a
- Jacob Carlborg (6/9) Oct 25 2015 I've been waiting for this PR [1] to get merged before implementing
- Edmund Smith (37/103) Oct 26 2015 You could also emulate constant matching using default parameters
- Jacob Carlborg (9/36) Oct 26 2015 Why not just use a value as an extra argument:
- TheFlyingFiddle (45/87) Oct 26 2015 I like this you could go further with this to allow any number of
- Edmund Smith (55/61) Oct 26 2015 The key difference with (exhaustive) pattern matching is that it
- TheFlyingFiddle (28/45) Oct 26 2015 What I meant is that I don't really see the point in optionals
- Kagamin (18/21) Oct 27 2015 Probably possible:
- TheFlyingFiddle (79/101) Oct 27 2015 Sure that would work but i don't see that it's different then an
- Kagamin (4/8) Oct 27 2015 If pattern matching is the only way, the get function above will
- Meta (26/46) Oct 27 2015 This can arguably already be done cleaner in D.
- TheFlyingFiddle (14/40) Oct 27 2015 Yes this is much cleaner. But it does not really force a user to
- cym13 (26/60) Oct 24 2015 Although it doesn't exactly fit the problem at hand I'd like to
- Jacob Carlborg (6/35) Oct 25 2015 Both of these can be implemented in library code. Although the syntax
- Dmitry Olshansky (11/26) Oct 25 2015 I humbly believe that D may just add special re-write rule to the switch...
- TheFlyingFiddle (3/8) Oct 25 2015 I don't think I understand this, could you elaborate?
- Jacob Carlborg (9/13) Oct 26 2015 Do you think that could handle all different type of patterns? For
- Dmitry Olshansky (8/19) Oct 26 2015 Scala does it with a bit of re-writes and partially hard-wired logic
- Kagamin (1/1) Oct 26 2015 For Option see https://w0rp.com/project/dstruct/dstruct/option/
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
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 = VarientCombined 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
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
On 25/10/15 6:45 PM, Nerve wrote:On Sunday, 25 October 2015 at 05:05:47 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.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.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
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
On 25/10/15 7:05 PM, Nerve wrote:On Sunday, 25 October 2015 at 05:53:32 UTC, Rikki Cattermole wrote: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.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?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
On 25/10/15 7:15 PM, Rikki Cattermole wrote:On 25/10/15 7:05 PM, Nerve wrote: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; } }On Sunday, 25 October 2015 at 05:53:32 UTC, Rikki Cattermole wrote: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.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?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
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: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); }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" }
Oct 24 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.
Oct 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.
Oct 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).
Oct 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
Oct 25 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: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).On Sunday, 25 October 2015 at 05:05:47 UTC, Rikki Cattermole 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); }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" }
Oct 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
Oct 26 2015
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.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);Why not just use a value as an extra argument: v.match!( 7, (int i) => "Lucky number 7" );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
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: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.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
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: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.On Monday, 26 October 2015 at 11:40:09 UTC, Edmund Smith wrote: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: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
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
On Tuesday, 27 October 2015 at 07:55:46 UTC, Kagamin wrote:On Monday, 26 October 2015 at 16:42:27 UTC, TheFlyingFiddle wrote: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.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
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
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
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
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: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"); } }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
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
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
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
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
On 26-Oct-2015 12:16, Jacob Carlborg wrote:On 2015-10-25 19:23, Dmitry Olshansky wrote: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 OlshanskyI 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.
Oct 26 2015
For Option see https://w0rp.com/project/dstruct/dstruct/option/
Oct 26 2015