Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upReconcile syntax of "match" expression based on LDM feedback #8818
Comments
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gafter
Feb 17, 2016
Member
@MadsTorgersen Can we meet sometime this week to make some tentative calls for the prototype?
|
@MadsTorgersen Can we meet sometime this week to make some tentative calls for the prototype? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
Feb 17, 2016
Not sure if you really want the opinions on non-LDM members here, but I'll offer them anyway (I assume it can be deleted if this is an unhelpful comment):
Are we using switch or match?
If match can be used in a non-breaking fashion, please use that. It'll make teaching the concept of match expressions to others easier if it has a different name to switch
default: or case *: or both?
Please, please, please don't use case *. This closes off the option for using * as a throw-away variable (the equivalent to F#'s _) in later releases of C#.
DavidArno
commented
Feb 17, 2016
|
Not sure if you really want the opinions on non-LDM members here, but I'll offer them anyway (I assume it can be deleted if this is an unhelpful comment):
If
Please, please, please don't use |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
HaloFour
Feb 18, 2016
Another peanut-gallery opinion, feel free to forward to /dev/null.
I think I might prefer match to switch as it allows avoiding the baggage that may otherwise be inherited by reusing switch, even though the syntax and context would be quite different.
I prefer case * to default for much of the same reason, although if you go with match it probably doesn't matter much. I don't see why it would preclude the possibility of implementing a feature like #8074 in the future considering that the syntactic contexts are different.
Does it make sense for an incomplete match in an expression to result in anything other than an exception? I'm not sure that it does. As such I think that the compiler should try to enforce that the match is complete and silently emit a wildcard pattern that throws unless one is already defined.
HaloFour
commented
Feb 18, 2016
|
Another peanut-gallery opinion, feel free to forward to I think I might prefer I prefer Does it make sense for an incomplete match in an expression to result in anything other than an exception? I'm not sure that it does. As such I think that the compiler should try to enforce that the match is complete and silently emit a wildcard pattern that throws unless one is already defined. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Feb 18, 2016
Contributor
I think I'd prefer switch instead of match as long as they feature similar possibilities. Currently as it is specified, it would be not possible to write multiple cases (and default case) in the switch expression even though you have chosen switch to keep them closer, syntactically. While case * is something that would be expected in a pattern-matching construct, a default case is idiomatic C# and IMO shouldn't be excluded from switch expression. I think it's more of a preference but it doesn't mean that one should be discarded in favor of the other.
|
I think I'd prefer |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gafter
Feb 18, 2016
Member
The main reason that I resist default: in a match is that we want the match cases to be placed in order. In a switch statement, you can put the default anywhere among the cases, but it always matches last. We want to force it to be last. But I suppose we could just require it to be in the last position only for a match expression.
I think we're likely to change the keyword from switch to the contextual keyword match for the match expression.
|
The main reason that I resist I think we're likely to change the keyword from |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Feb 18, 2016
Contributor
If it's likely to change the keyword to match, I can tell there would be no need for commas and case expressions can be represented by match <pattern> which doesn't need to disallow chaining.
Just a quick question, ordering problem doesn't apply to switch statement already? I mean the following would be useless, because name woudn't be definitely assigned anyway,
switch(...) {
default:
case Student(var name):
break;
}Why it cannot be applied to match expressions as well?
|
If it's likely to change the keyword to Just a quick question, ordering problem doesn't apply to switch statement already? I mean the following would be useless, because switch(...) {
default:
case Student(var name):
break;
}Why it cannot be applied to match expressions as well? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
Feb 18, 2016
The main reason that I resist default: in a match is that we want the match cases to be placed in order. In a switch statement, you can put the default anywhere among the cases, but it always matches last. We want to force it to be last. But I suppose we could just require it to be in the last position only for a match expression.
This surely has to apply to a match statement too? If the switch is using the new pattern-matching features, then it's a match statement and thus the order of the cases becomes important and the default must therefore be last. This isn't just an issue for match expressions.
DavidArno
commented
Feb 18, 2016
This surely has to apply to a match statement too? If the |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gafter
Feb 18, 2016
Member
@DavidArno No, we're not going to change the fact that a switch statement treats the default case as a "last fallback" no matter where it appears in the syntax. The addition of a pattern-matching construct somewhere in the switch won't change that.
If it's likely to change the keyword to match, I can tell there would be no need for commas and case expressions can be represented by match which doesn't need to disallow chaining.
I have no idea what this means. What syntax are you imagining?
|
@DavidArno No, we're not going to change the fact that a
I have no idea what this means. What syntax are you imagining? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Feb 18, 2016
Contributor
// as originally proposed
var result = t match(case P1: e1 case P2: e2); // no comma
var result = t match P : e;
// instead of
var result = t case P : e;case-expression:
shift-expressioncasepattern:shift-expression
Becomes,
case-expression:
relational-expressionmatchpattern:shift-expression
// as originally proposed
var result = t match(case P1: e1 case P2: e2); // no comma
var result = t match P : e;
// instead of
var result = t case P : e;
Becomes,
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
Feb 18, 2016
So, taking the example from xxx:
switch (e) {
case Mult(Const(0), *): return Const(0);
case Mult(*, Const(0)): return Const(0);
case Mult(Const(1), var x): return Simplify(x);
case Mult(var x, Const(1)): return Simplify(x);
case Mult(Const(var l), Const(var r)): return Const(l*r);
case Add(Const(0), var x): return Simplify(x);
case Add(var x, Const(0)): return Simplify(x);
case Add(Const(var l), Const(var r)): return Const(l+r);
case Neg(Const(var k)): return Const(-k);
default: return e;
}I could change that to:
switch (e) {
case Mult(Const(0), *): return Const(0);
default: return e;
case Mult(*, Const(0)): return Const(0);
case Mult(Const(1), var x): return Simplify(x);
case Mult(var x, Const(1)): return Simplify(x);
case Mult(Const(var l), Const(var r)): return Const(l*r);
case Add(Const(0), var x): return Simplify(x);
case Add(var x, Const(0)): return Simplify(x);
case Add(Const(var l), Const(var r)): return Const(l+r);
case Neg(Const(var k)): return Const(-k);
}and it will not affect the functionality? Will default just be shifted to the end of the list by the compiler therefore? That will be highly confusing: "pattern matching switch statements test each case in order, stopping when a pattern matches, except for default, which will be treated as the last expression, no matter where you put it in the list" That's nasty.
DavidArno
commented
Feb 18, 2016
|
So, taking the example from xxx: switch (e) {
case Mult(Const(0), *): return Const(0);
case Mult(*, Const(0)): return Const(0);
case Mult(Const(1), var x): return Simplify(x);
case Mult(var x, Const(1)): return Simplify(x);
case Mult(Const(var l), Const(var r)): return Const(l*r);
case Add(Const(0), var x): return Simplify(x);
case Add(var x, Const(0)): return Simplify(x);
case Add(Const(var l), Const(var r)): return Const(l+r);
case Neg(Const(var k)): return Const(-k);
default: return e;
}I could change that to: switch (e) {
case Mult(Const(0), *): return Const(0);
default: return e;
case Mult(*, Const(0)): return Const(0);
case Mult(Const(1), var x): return Simplify(x);
case Mult(var x, Const(1)): return Simplify(x);
case Mult(Const(var l), Const(var r)): return Const(l*r);
case Add(Const(0), var x): return Simplify(x);
case Add(var x, Const(0)): return Simplify(x);
case Add(Const(var l), Const(var r)): return Const(l+r);
case Neg(Const(var k)): return Const(-k);
}and it will not affect the functionality? Will |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
HaloFour
Feb 18, 2016
@DavidArno That's the baggage inherited from switch and default. It could be argued that pattern matching doesn't fit well with the semantics of switch considering that order is not supposed to matter. Maybe match should be used for statement pattern matching also. Ditch the baggage altogether.
HaloFour
commented
Feb 18, 2016
|
@DavidArno That's the baggage inherited from |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
Feb 18, 2016
Yes, that's exactly what I'd like to see. The current switch statement is a wholly different thing to a match statement. So don't try and merge the two into some amorphous mess: make them two distinct things.
Further I'm sure that any breaking change concerns around using the match keyword will prove easier to solve than trying to make pattern matching work fully with goto and the like.
DavidArno
commented
Feb 18, 2016
|
Yes, that's exactly what I'd like to see. The current switch statement is a wholly different thing to a match statement. So don't try and merge the two into some amorphous mess: make them two distinct things. Further I'm sure that any breaking change concerns around using the match keyword will prove easier to solve than trying to make pattern matching work fully with goto and the like. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gafter
Feb 18, 2016
Member
@DavidArno Those are already the semantics of the existing switch: it treats the cases as if in order (since they cannot overlap, this is trivially so), except default which is always handled last. The funny order of default is one of two slightly unfortunate effects of using the existing switch statement syntax for pattern-matching, the other being the treatment of goto case. I would be fine adding a warning (or perhaps even an error) when a default appears anywhere but the last position in a switch that contains any pattern-matching syntax.
|
@DavidArno Those are already the semantics of the existing |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bondsbw
Feb 18, 2016
Would match provide exhaustive matching (except in the case that case *: or default: is provided)? And how would this be implemented, using sealed (to ensure it cannot be subclassed elsewhere)?
I don't think I care for match unless it fundamentally provides value such as this. I would hate to use up that keyword and deny a future implementation that can properly guarantee exhaustive matching.
bondsbw
commented
Feb 18, 2016
|
Would I don't think I care for |
gafter
self-assigned this
Feb 18, 2016
gafter
added this to the
2.0 (Preview) milestone
Feb 18, 2016
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gafter
Feb 18, 2016
Member
@bondsbw That is precisely the question asked in this issue (fifth question in the list).
|
@bondsbw That is precisely the question asked in this issue (fifth question in the list). |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
AdamSpeight2008
Feb 18, 2016
Contributor
match gets my vote. (eg #5202)
match ( e )
{
|: Mult(Const(0), *),
Mult(*, Const(0)) => Const(0);
|: Mult(Const(1), var x),
Mult(var x, Const(1)) => Simplify(x);
|: Mult(Const(var l), Const(var r)) => Const(l*r);
|: Add(Const(0), var x),
Add(var x, Const(0)) => Simplify(x);
|: Add(Const(var l), Const(var r)) => Const(l+r);
|: Neg(Const(var k)) => Const(-k);
default:
return e;
}'default:required in cases where compiler can prove completeness of the matches. If the compiler proves completeness and there is adefault:that section section of code is mark asunreachable?orunnecessary'.
|
match ( e )
{
|: Mult(Const(0), *),
Mult(*, Const(0)) => Const(0);
|: Mult(Const(1), var x),
Mult(var x, Const(1)) => Simplify(x);
|: Mult(Const(var l), Const(var r)) => Const(l*r);
|: Add(Const(0), var x),
Add(var x, Const(0)) => Simplify(x);
|: Add(Const(var l), Const(var r)) => Const(l+r);
|: Neg(Const(var k)) => Const(-k);
default:
return e;
}'default: |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bondsbw
Feb 18, 2016
@gafter Sorry I realized later that's what you meant by "complete", but I did want to throw my (perhaps unsolicited and humble) opinion in that non-exhaustive/incomplete matching isn't worth locking down a new keyword.
bondsbw
commented
Feb 18, 2016
|
@gafter Sorry I realized later that's what you meant by "complete", but I did want to throw my (perhaps unsolicited and humble) opinion in that non-exhaustive/incomplete matching isn't worth locking down a new keyword. |
gafter
added
the
0 - Backlog
label
Feb 24, 2016
gafter
modified the milestones:
2.0 (RC),
2.0 (Preview)
Feb 25, 2016
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
JohnnyBravo75
Mar 21, 2016
I vote for 'switch' because it is an already used construct in c#.
Its syntax is not the best, but its better to reuse it and give it more power with matching than introducing match as an extra keyword.
JohnnyBravo75
commented
Mar 21, 2016
|
I vote for 'switch' because it is an already used construct in c#. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
AdamSpeight2008
Mar 21, 2016
Contributor
My concerns with using switch is that it changes the semantics of it, potentially break compatibility with existing code. Using match doesn't as well as indicating that this block is a pattern-match.
|
My concerns with using |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gafter
Mar 21, 2016
Member
@AdamSpeight2008 We will not change the meaning of existing code. This issue is about a new syntactic form for an expression.
|
@AdamSpeight2008 We will not change the meaning of existing code. This issue is about a new syntactic form for an expression. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
AdamSpeight2008
Mar 21, 2016
Contributor
@gafter Say if I have pre-existing code using switch then recompile it with a compiler that uses pattern-matching switch. Would it compile to the same code, or different.
|
@gafter Say if I have pre-existing code using |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
AdamSpeight2008
Mar 21, 2016
Contributor
The following would silently compile in pattern-matching switch compiler
x = switch ( )
{
}
where as in existing compiler would fail.
|
The following would silently compile in
where as in existing compiler would fail. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
HaloFour
Mar 21, 2016
That would not be a legal switch expression. The switch keyword would be used as an operator following the operand. I also seriously doubt that an empty pattern list would be legal.
var result = operand switch (case Foo: "Foo" case Bar: "Bar" case *: "Other");A switch statement, on the other hand, cannot be assigned to a variable. You'd have to assign the variable within the case labels:
string result;
switch (operand) {
case Foo:
result = "Foo";
break;
case Bar:
result = "Bar";
break;
default:
result = "Other";
break;
}
HaloFour
commented
Mar 21, 2016
|
That would not be a legal var result = operand switch (case Foo: "Foo" case Bar: "Bar" case *: "Other");A string result;
switch (operand) {
case Foo:
result = "Foo";
break;
case Bar:
result = "Bar";
break;
default:
result = "Other";
break;
} |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
AdamSpeight2008
Mar 21, 2016
Contributor
@HaloFour
It for illustrative purposes, the original is saved with the pre-existing error. Yet opening it in a PMS compiler the pre-existing isn't an error.
|
@HaloFour |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
paulomorgado
Mar 21, 2016
@gafter, why is that so important that the default case be the last? Just to keep the semantics that they are enforced in order?
If it's not to expensive to have a special case default: when using pattern matching, I would stick with it.
Have you tried analyzing real-life projects to see how often default: appears and it's not the last option?
paulomorgado
commented
Mar 21, 2016
|
@gafter, why is that so important that the default case be the last? Just to keep the semantics that they are enforced in order? If it's not to expensive to have a special case Have you tried analyzing real-life projects to see how often |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
HaloFour
Mar 21, 2016
Well your example would most definitely be an error both pre- and post-pattern matching, but that's beside the point. Virtually every new feature results in previously non-compiling code to now compile. That's very explicitly not a breaking change. The fact that switch can't be used in some contexts in C# 6.0 doesn't mean that it can never be considered for use in those contexts in future versions. There is no example of pattern matching switch, in statement or expression form, that would be considered legal code today.
HaloFour
commented
Mar 21, 2016
|
Well your example would most definitely be an error both pre- and post-pattern matching, but that's beside the point. Virtually every new feature results in previously non-compiling code to now compile. That's very explicitly not a breaking change. The fact that |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
HaloFour
Mar 21, 2016
I've seen it used where the developer wanted to keep the cases in order (by enum names or whatever) and the default behavior would be identical to one of the cases so they wanted to take advantage of fall-through rather than duplicate code:
switch (x) {
case MyEnum.A:
// ...
break;
case MyEnum.B:
default:
// ...
break;
case MyEnum.C:
// ...
break;
}This was never an issue previously as there was never any overlap between the cases and the comparisons were always very simple. Pattern matching will introduce the potential for overlap and user-defined evaluation which makes the order of evaluation important. Since the difference between a pre-pattern matching switch and a post-pattern matching switch is effectively just the introduction of a single case that could change the above code in unexpected ways, specifically MyEnum.C might never be evaluated since it comes after the default case, although more likely it would be a compile-time error since MyEnum.C is subsumed by the default case.
HaloFour
commented
Mar 21, 2016
|
I've seen it used where the developer wanted to keep the cases in order (by enum names or whatever) and the default behavior would be identical to one of the cases so they wanted to take advantage of fall-through rather than duplicate code: switch (x) {
case MyEnum.A:
// ...
break;
case MyEnum.B:
default:
// ...
break;
case MyEnum.C:
// ...
break;
}This was never an issue previously as there was never any overlap between the cases and the comparisons were always very simple. Pattern matching will introduce the potential for overlap and user-defined evaluation which makes the order of evaluation important. Since the difference between a pre-pattern matching switch and a post-pattern matching switch is effectively just the introduction of a single case that could change the above code in unexpected ways, specifically |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
paulomorgado
Mar 21, 2016
@HaloFour, those would be surprised to see that they can't place default: anywhere they want to when it cames to pattern matching, but their existing code wouldn't break,
paulomorgado
commented
Mar 21, 2016
|
@HaloFour, those would be surprised to see that they can't place |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
AdamSpeight2008
Mar 21, 2016
Contributor
@HaloFour The confusion is that I see the patten-matching in functional languages as a function. Then the `return' being used to a value from it. In the current implementation (as is) the returned value is returned not the "pattern match" but the enclosing method.
I think we would have to do something like the following to replicate that.
string result = ()=>{
switch (operand)
{
case Foo: return "Foo";
case Bar: return "Bar";
default: return "Other";
}();
}I think we should consider would it be better to treat like a lambda function block.
string result = switch (operand)
{
case Foo: return "Foo";
case Bar: return "Bar";
default: return "Other";
};
}|
@HaloFour The confusion is that I see the patten-matching in functional languages as a function. Then the `return' being used to a value from it. In the current implementation (as is) the returned value is returned not the "pattern match" but the enclosing method. I think we would have to do something like the following to replicate that. string result = ()=>{
switch (operand)
{
case Foo: return "Foo";
case Bar: return "Bar";
default: return "Other";
}();
}I think we should consider would it be better to treat like a lambda function block. string result = switch (operand)
{
case Foo: return "Foo";
case Bar: return "Bar";
default: return "Other";
};
} |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
HaloFour
Mar 21, 2016
Such syntax was already proposed and dismissed in #206, specifically the "hijacking" of the return statement. Fairly sure that's not on the table and that this issue is specifically to address the otherwise minor syntactic issues around implementing proposal #5154, or rather it's evolution since being absorbed by the greater pattern matching proposal.
HaloFour
commented
Mar 21, 2016
|
Such syntax was already proposed and dismissed in #206, specifically the "hijacking" of the |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Mar 21, 2016
Contributor
This was never an issue previously as there was never any overlap between the cases and the comparisons were always very simple.
This is because you are assuming that default is a synonym for case *. But it's not (shouldn't be), for example,
switch(expr) {
case *: break;
case Const(0): break; // ERROR: subsumption: previous case convers all the possible values
case Mult(var value, 0): break;
}
switch(expr) {
default:
case Const(0): /* (1) */ break; // perfectly fine, when expr is Const and otherwise
case Mult(var value, 0): break;
}
// in contrast, if you have to put the default at the end you must duplicate the (1) in both cases,
switch(expr) {
case Const(0): /* (1) */ break; // when expr is Const
case Mult(var value, 0): break; // when expr is Mult
default: /* again (1) */ break; // otherwise -- this is where default and case star are the same
}
// and of course
switch(expr) {
default:
case Const(var value): break; // ERROR: value is not definitely assigned
}
This is because you are assuming that switch(expr) {
case *: break;
case Const(0): break; // ERROR: subsumption: previous case convers all the possible values
case Mult(var value, 0): break;
}
switch(expr) {
default:
case Const(0): /* (1) */ break; // perfectly fine, when expr is Const and otherwise
case Mult(var value, 0): break;
}
// in contrast, if you have to put the default at the end you must duplicate the (1) in both cases,
switch(expr) {
case Const(0): /* (1) */ break; // when expr is Const
case Mult(var value, 0): break; // when expr is Mult
default: /* again (1) */ break; // otherwise -- this is where default and case star are the same
}
// and of course
switch(expr) {
default:
case Const(var value): break; // ERROR: value is not definitely assigned
} |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Mar 21, 2016
Contributor
var x = expr match(
default:
case Foo(1): e1,
case Foo(var value): e2,
);
var x = expr match(
case Foo(1): e1,
case Foo(var value): e2,
case *: e1,
);
var x = expr match(
default:
case Foo(1): e1,
case Foo(var value): e2,
);
var x = expr match(
case Foo(1): e1,
case Foo(var value): e2,
case *: e1,
);
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bondsbw
commented
Mar 21, 2016
|
Same thing as var x = expr match(
case Foo(var value): e2,
case *: e1,
); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
Mar 21, 2016
No, that's not the same. With your example, Foo(1) would result in e2.
@alrz,
I really feel uncomfortable with that default, but can't really put my finger on why and it could indeed be useful. I think I'd prefer something like the following, but I'm not sure if it'll fit the spec:
var x = expr match(
case Foo(1) || default: e1,
case Foo(var value): e2
);
DavidArno
commented
Mar 21, 2016
|
No, that's not the same. With your example, @alrz, var x = expr match(
case Foo(1) || default: e1,
case Foo(var value): e2
); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Mar 21, 2016
Contributor
@DavidArno I would not prefer any new syntax here, actually it is a good thing that it'd be similar to switch syntax as for default and case labels. So you can simply rewrite your switch as a match whenever it makes sense,
T value;
switch(...) {
case ...:
case ...:
value = foo;
break;
case ...:
value = bar;
break;
}So I don't see why it feels uncomfortable, because we already have code like this in switch.
Anyhow, I'd be ok if this doesn't get implemented in C# 7 timeframe, because it is kind of related to #6235:
// example from pattern spec
Expr Simplify(Expr e) => e match(
case Mult(Const(0), *):
case Mult(*, Const(0)): Const(0),
case Mult(Const(1), var x):
case Mult(var x, Const(1)): Simplify(x),
case Mult(Const(var l), Const(var r)): Const(l*r),
case Add(Const(0), var x):
case Add(var x, Const(0)): Simplify(x),
case Add(Const(var l), Const(var r)): Const(l+r),
case Neg(Const(var k)): Const(-k),
default: e
);So that multiple cases behave like an OR pattern.
|
@DavidArno I would not prefer any new syntax here, actually it is a good thing that it'd be similar to T value;
switch(...) {
case ...:
case ...:
value = foo;
break;
case ...:
value = bar;
break;
}So I don't see why it feels uncomfortable, because we already have code like this in Anyhow, I'd be ok if this doesn't get implemented in C# 7 timeframe, because it is kind of related to #6235: // example from pattern spec
Expr Simplify(Expr e) => e match(
case Mult(Const(0), *):
case Mult(*, Const(0)): Const(0),
case Mult(Const(1), var x):
case Mult(var x, Const(1)): Simplify(x),
case Mult(Const(var l), Const(var r)): Const(l*r),
case Add(Const(0), var x):
case Add(var x, Const(0)): Simplify(x),
case Add(Const(var l), Const(var r)): Const(l+r),
case Neg(Const(var k)): Const(-k),
default: e
);So that multiple cases behave like an OR pattern. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
Mar 21, 2016
Ah thanks, I thought it looked familiar and that was why I was uncomfortable with it. I'd far prefer || be used for OR patterns:
Expr Simplify(Expr e) => e match(
case Mult(Const(0), *) || case Mult(*, Const(0)): Const(0),
case Mult(Const(1), var x) || case Mult(var x, Const(1)): Simplify(x),
case Mult(Const(var l), Const(var r)): Const(l*r),
case Add(Const(0), var x) || case Add(var x, Const(0)): Simplify(x),
case Add(Const(var l), Const(var r)): Const(l+r),
case Neg(Const(var k)): Const(-k),
default: e
);To my mind, the less it looks like a switch, the better as I make no secret of the fact I positively hate the switch statement and what they plan to do with it when adding pattern matching. But, as you say, OR patterns are probably a debate for after C# 7 is released.
DavidArno
commented
Mar 21, 2016
|
Ah thanks, I thought it looked familiar and that was why I was uncomfortable with it. I'd far prefer Expr Simplify(Expr e) => e match(
case Mult(Const(0), *) || case Mult(*, Const(0)): Const(0),
case Mult(Const(1), var x) || case Mult(var x, Const(1)): Simplify(x),
case Mult(Const(var l), Const(var r)): Const(l*r),
case Add(Const(0), var x) || case Add(var x, Const(0)): Simplify(x),
case Add(Const(var l), Const(var r)): Const(l+r),
case Neg(Const(var k)): Const(-k),
default: e
);To my mind, the less it looks like a |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Mar 21, 2016
Contributor
@DavidArno When you explicitly use an OR pattern you don't need to mention case keyword for the second time, because it would be defined under pattern and case is not part of that. See #6235.
|
@DavidArno When you explicitly use an OR pattern you don't need to mention |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
gafter
Mar 22, 2016
Member
@gafter Say if I have pre-existing code using
switchthen recompile it with a compiler that usespattern-matching switch. Would it compile to the same code, or different.
Same, plus or minus epsilon.
Same, plus or minus epsilon. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Mar 22, 2016
Contributor
I'd like to note that there is no other expressions that use parenteses and also support trailing commas,
// record declaration - parens and no trailing comma
class Person(
string FirstName,
string LastName
);
// constructor and invocations, parens and no trailing comma
object F() => new Person(
"FirstName",
"LastName"
);
// initializer -- curly braces, trailing comma
object F() => new Foo {
Bar = value,
};
// with -- curly braces, trailing comma
object F(Person person) => person with {
FirstName = "FirstName",
LastName = "LastName",
};
// match -- curly braces, trailing comma
object F(object arg) => arg match {
case Foo: expr,
case Bar: expr,
};So I think it would be more consistent if we use curly braces for match as long as we require comma between cases.
In defense of tuples, if they ever support trailing commas that's because a oneple would be ambiguous with a parenthesized-expression, in any case, if you add more items you are practically changing the type; this is not true for with, match or object initializers.
|
I'd like to note that there is no other expressions that use parenteses and also support trailing commas, // record declaration - parens and no trailing comma
class Person(
string FirstName,
string LastName
);
// constructor and invocations, parens and no trailing comma
object F() => new Person(
"FirstName",
"LastName"
);
// initializer -- curly braces, trailing comma
object F() => new Foo {
Bar = value,
};
// with -- curly braces, trailing comma
object F(Person person) => person with {
FirstName = "FirstName",
LastName = "LastName",
};
// match -- curly braces, trailing comma
object F(object arg) => arg match {
case Foo: expr,
case Bar: expr,
};So I think it would be more consistent if we use curly braces for In defense of tuples, if they ever support trailing commas that's because a oneple would be ambiguous with a parenthesized-expression, in any case, if you add more items you are practically changing the type; this is not true for |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
Mar 22, 2016
To keep it consistent, if braces were used, trailing commas would have to be supported too, which would mean extending that embarrassingly bad design decision into more languages features. So the best consistent option from your examples is parentheses with no trailing comma allowed.
DavidArno
commented
Mar 22, 2016
|
To keep it consistent, if braces were used, trailing commas would have to be supported too, which would mean extending that embarrassingly bad design decision into more languages features. So the best consistent option from your examples is parentheses with no trailing comma allowed. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Trailing comma is an embarrassingly bad design decision. Noted. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
The final trailing comma shouldn't be needed. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Mar 22, 2016
Contributor
You guys know that it's optional right? And I'm not proposing it, it is from spec draft.
|
You guys know that it's optional right? And I'm not proposing it, it is from spec draft. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
Mar 23, 2016
I'm aware it's optional; that's it's problem. When looking at a piece of code with a trailing comma, one must ask: is it there because the author likes to use them, because they forgot to refactor, or because something has been missed and thus it could be a bug. They create a serious hindrance to readability just to pander to developers who want their lives made fractionally easier when writing the code.
DavidArno
commented
Mar 23, 2016
|
I'm aware it's optional; that's it's problem. When looking at a piece of code with a trailing comma, one must ask: is it there because the author likes to use them, because they forgot to refactor, or because something has been missed and thus it could be a bug. They create a serious hindrance to readability just to pander to developers who want their lives made fractionally easier when writing the code. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Mar 23, 2016
Contributor
@orthoxerox Do I need a sarcasm sign?
@DavidArno Perhaps you should research why it is useful in the first place and why do we care about it (or not). This is nothing new in C# or programming language design overall. I don't think that I need to explain the basics. Again, I'm not proposing it and my suggestion is based on the current design (of C# not just match).
|
@orthoxerox Do I need a sarcasm sign? @DavidArno Perhaps you should research why it is useful in the first place and why do we care about it (or not). This is nothing new in C# or programming language design overall. I don't think that I need to explain the basics. Again, I'm not proposing it and my suggestion is based on the current design (of C# not just |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
orthoxerox
Mar 23, 2016
Contributor
@alrz Mornings are never kind to me
I like both trailing commas and curlies.
|
@alrz Mornings are never kind to me I like both trailing commas and curlies. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
Mar 23, 2016
You may rest assured that I have researched it. There are three main use cases:
- It makes it easier to write and edit lists of data;
- It makes it easier to write code generators;
- It makes it easier to view source change diffs using command line tools.
The first two are only of use to developers who value making their lives easier when writing code over making the lives of readers of their code, easier. The third doesn't really apply to 99.9%of C# developers.
Mistakes will always be made in designing languages and decisions taken 10-15 years ago will often be viewed as wrong with the benefit of hindsight. C# is a conservative language and avoids breaking changes. So we are stuck with trailing commas being supported for some existing features. That doesn't mean they should be used though. And "we have always done it like that" is a very silly reason to repeat those mistakes with new features.
Ergo, what C# currently does isn't important; doing match properly, is.
DavidArno
commented
Mar 23, 2016
|
You may rest assured that I have researched it. There are three main use cases:
The first two are only of use to developers who value making their lives easier when writing code over making the lives of readers of their code, easier. The third doesn't really apply to 99.9%of C# developers. Mistakes will always be made in designing languages and decisions taken 10-15 years ago will often be viewed as wrong with the benefit of hindsight. C# is a conservative language and avoids breaking changes. So we are stuck with trailing commas being supported for some existing features. That doesn't mean they should be used though. And "we have always done it like that" is a very silly reason to repeat those mistakes with new features. Ergo, what C# currently does isn't important; doing |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
HaloFour
Mar 23, 2016
@DavidArno Being internally consistent is even more important. The syntax doesn't affect the behavior of match (or switch) in the least, this is a personal style choice. If you don't like it, you're free to both not use it and to write an analyzer which errors if it encounters it.
HaloFour
commented
Mar 23, 2016
|
@DavidArno Being internally consistent is even more important. The syntax doesn't affect the behavior of |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
Mar 23, 2016
Consistency is important, so use () for match as the convention there is not to have trailing commas
DavidArno
commented
Mar 23, 2016
|
Consistency is important, so use |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Mar 23, 2016
Contributor
@DavidArno without comma it will be ambiguous with case expressions. There is a reason for every bit of the syntax. You should try to understand this.
|
@DavidArno without comma it will be ambiguous with case expressions. There is a reason for every bit of the syntax. You should try to understand this. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
DavidArno
commented
Mar 23, 2016
|
How would a trailing comma remove ambiguity? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
HaloFour
Mar 23, 2016
@DavidArno That works for me.
I'm kind of on the fence about the whole brace v. parenthesis bit. I think parenthesis looks fine for a handful of patterns but beyond that I think braces would look nicer. It's like how a method with a huge number of arguments can look awkward. But it's really a minor concern.
I'm much more concerned about the behavior and features of the expression than it's specific syntax. I'd be thrilled with guillemets and poop emojis if they delivered active patterns and proper AND/OR patterns.
HaloFour
commented
Mar 23, 2016
|
@DavidArno That works for me. I'm kind of on the fence about the whole brace v. parenthesis bit. I think parenthesis looks fine for a handful of patterns but beyond that I think braces would look nicer. It's like how a method with a huge number of arguments can look awkward. But it's really a minor concern. I'm much more concerned about the behavior and features of the expression than it's specific syntax. I'd be thrilled with guillemets and poop emojis if they delivered active patterns and proper AND/OR patterns. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
alrz
Mar 23, 2016
Contributor
@DavidArno Oh you are talking about just the trailing comma. Well, that doesn't seem to be up for discussion here, it is about using commas or not at all. And if they prefer to not use it, perhaps they should redesign case expressions to be distinguishable from case labels.
case_expression
- : shift_expression 'case' pattern ':' shift_expression
+ : relational_expression 'match' pattern ':' shift_expression
;|
@DavidArno Oh you are talking about just the trailing comma. Well, that doesn't seem to be up for discussion here, it is about using commas or not at all. And if they prefer to not use it, perhaps they should redesign case expressions to be distinguishable from case labels. case_expression
- : shift_expression 'case' pattern ':' shift_expression
+ : relational_expression 'match' pattern ':' shift_expression
; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bondsbw
Mar 23, 2016
Braces are used in C# when the contents are typically expected to be provided on multiple lines. Parentheses are used otherwise.
And I believe that most code formatters make the assumption that parentheses are intended to span one line (assuming it's not too long). I made a fluent syntax for an area of my project that creates a hierarchical outline:
TableOfContents._
(
BeforeYouBegin,
Introduction,
CompilerConcepts._
(
Lexer,
Parser,
Checker,
Emitter
),
CreatingYourOwnLanguage._
(
Design,
Optimization,
Tooling
)
);One annoyance is that Resharper wants to reformat the parentheses every time I edit anything, because in most other cases that's the expectation. I suspect other tooling would be similar.
So for match expressions, I feel braces are definitely more consistent than parentheses.
bondsbw
commented
Mar 23, 2016
|
Braces are used in C# when the contents are typically expected to be provided on multiple lines. Parentheses are used otherwise. And I believe that most code formatters make the assumption that parentheses are intended to span one line (assuming it's not too long). I made a fluent syntax for an area of my project that creates a hierarchical outline: TableOfContents._
(
BeforeYouBegin,
Introduction,
CompilerConcepts._
(
Lexer,
Parser,
Checker,
Emitter
),
CreatingYourOwnLanguage._
(
Design,
Optimization,
Tooling
)
);One annoyance is that Resharper wants to reformat the parentheses every time I edit anything, because in most other cases that's the expectation. I suspect other tooling would be similar. So for match expressions, I feel braces are definitely more consistent than parentheses. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
AdamSpeight2008
Mar 23, 2016
Contributor
Didn't you say the comma was optional? Hence no abiquity.
-------- Original Message --------
From:David Arno notifications@github.com
Sent:Wed, 23 Mar 2016 14:29:53 +0000
To:dotnet/roslyn roslyn@noreply.github.com
Cc:Adam Speight adam.speight@virgin.net
Subject:Re: [roslyn] Reconcile syntax of "match" expression based on LDM feedback (#8818)
How would a trailing comma remove ambiguity?
—
You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
|
Didn't you say the comma was optional? Hence no abiquity. -------- Original Message --------
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
aluanhaddad
Apr 1, 2016
Such syntax was already proposed and dismissed in #206, specifically the "hijacking" of the return statement.
The phrase "hijacking the meaning of the return statement" is forever etched into my brain. I will never live it down
If possible I would prefer that there be no delimiting ;s or ,s between cases in a match expression. For one thing, consider that the presence of any ; tokens in a switch statement is incidental, they are not part of the syntax of switch. For another, consider the syntax of the ternary operator.
Additionally, I don't think Resharper's, or any tool's, code formatting behavior should impact this proposal.
aluanhaddad
commented
Apr 1, 2016
The phrase "hijacking the meaning of the return statement" is forever etched into my brain. I will never live it down If possible I would prefer that there be no delimiting Additionally, I don't think Resharper's, or any tool's, code formatting behavior should impact this proposal. |
gafter
modified the milestones:
2.0 (RC),
3.0
Apr 27, 2016
gafter
added
the
Language-C#
label
Apr 28, 2016
gafter
added
the
Feature Request
label
May 17, 2016
alrz
referenced this issue
May 20, 2016
Closed
Pattern matching: completeness vs bool and enum types #11438
alrz
referenced this issue
Oct 31, 2016
Closed
Work Breakdown for "Proposed changes for deconstruction, declaration expressions, and discards" #14832
gafter
added this to Future Subfeatures
in Compiler: Pattern-Matching
Feb 20, 2017
DavidArno
referenced this issue
Mar 31, 2017
Open
Proposal: Permit trailing commas in method signatures and invocations #391
gafter
closed this
Apr 21, 2017
gafter
referenced this issue
Apr 21, 2017
Open
Reconcile syntax of "match" expression based on LDM feedback #487
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Issue moved to dotnet/csharplang #487 via ZenHub |
gafter commentedFeb 17, 2016
•
edited
Edited 1 time
-
gafter
edited Apr 15, 2016 (most recent)
We need the LDM (C# language design meeting attendees) to decide what the syntax of a "match expression" should be, and then we need to implement that.
Are we using
switchormatch?default:orcase *:or both?commas between cases?
Curly braces or parens?
Must a match expression be complete? If not, what happens when it isn't?
What about a single-case (irrefutable) match expression?