-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discussion: pattern matching versus switch statement #4944
Comments
If we do have to add a new statement form there would be a tension between (a) making as much like Before we "give up" and define a new statement form, is there something we can do to address the problem described above? |
Don't the case labels have to be a constant value of a particular set of types (sbyte, byte, short, ushort, int, uint, long, ulong, bool, char, string, or an enum type) or its corresponding nullable type as well? So as soon as you see that one of the labels isn't, you can be sure that this switch is a pattern-matching switch. If all of them are, it's a regular switch with an implicit conversion. |
In other words adding a switch case changes the meaning the the control expression? Yecch. |
The two use cases are literally incompatible. If you have class P.S. I still think new |
I think this problem can be solved with the following rules:
Decomposition:
becomes the same as
To get back to the example, Then, the optimizer can optimize a switch containing only cases of this type to the good old switch. Now, what should happen if you define an implicit operator for a type and a matching operator (Note: I personally would like a new |
Surely both are useful? How do I match to different actions (not functions) without having to return some |
@dsaf With |
Also wouldn't existing |
I believe that For example this is currently possible in vb.net but not C# Select Case True
Case a < 0
Case a = 0
Case a > 0
End Select
Select Case a
Case Is < 0
Case Is = 0
Case Is > 0
End Select
Select Case a
Case Integer.MinValue To -1
Case 0
Case 1 To Integer.MaxValue
End Select and thus better suited to being extended to include pattern-matching capabilities. Select Case (t1, t2)
Case (a As Integer, b As Integer) When a < b
Case (a As Double , b As Double ) When a < b
Case Else
End Select If I was to add pattern-matching to C# I would use a new construct ( match ( (t1,t2) )
{
¦: (int a, int b) when ( a <= b ) => return ;
¦: (double a, double b) when ( a <= b ) => return ;
¦: __ =>
} Remove the fall-through capability of the " match ( (t1,t2) )
{
¦: (int a, int b) when ( a <= b ),
(double a, double b) when ( a > b ) => return ;
¦: __ =>
} I'm basing the syntax on an existing .net language that have pattern matching F# and Nemerle. but use a different symbol |
That's not the way that But I like the direction you're taking. Here is my take on it:
I think this is nicely compatible because the existing "patterns" (switch cases) could never match a value of a user-defined type except by a converted value. And the compiler is already required to decide if a value of a given static type "could" match a pattern or not ( For example, for a control expression of type
This should retain the existing semantics for all existing switch statements, but still enable pattern-matching to be used in all its glory even for types that have such a conversion. There remains a separate question about whether or not we want an expression-based |
I am pretty satisfied that the above works as a solution. @MadsTorgersen suggested the alternate solution that user-defined implicit conversions be allowed on the value in all pattern matching contexts as part of the matching operation. I'm concerned about how that may interact with user-defined pattern-matching operators, but it is worth exploring. We have at least one or two solutions. Here's another problem. We want a pattern-matching-based switch (1)
{
case 1:
break;
case 2: // error: impossible to match
break;
} worse, it turns some code currently accepted with no diagnostics into an error: switch (1)
{
case 1:
case 2: // error: impossible to match
break;
} Do we give up on this checking, take a breaking change, or do something else? |
I guess this check could be downgraded to a warning. This way legacy code will compile for everyone except those who treat warnings as errors, and this kind of people will probably gladly fix their code. |
ReSharper generates warnings in such situations:
Incorrect format string will cause more problems than redundant or ignored case statements. |
Another route is taking a breaking change and shipping it with opt-in nullability as a C#7 - Super Strong Edition that I would personally gladly use :). |
Even a new warning is a breaking change for enough customers (who use warnings-as-errors) that we must treat it as a breaking change. |
Is it possible to link this check to the target .net version or some option that will be set for new projects by default? So, for example, if I continue to compile my program against 4.6 or lower, I won't get this error, but if I upgrade to 4.7 or 5.0 or whatever the new framework version is, I will? Or we can just all agree that a separate |
There seems to be the assumption that a code construct must be either an expression, either a statement, which is false. For example, Similarly, lambda's allow for expression bodies, as well as statement bodies. So, if a |
@KrisVandermotten C# inherits that from C/C++ where not all code constructs are expressions, such as control statements like I'd agree that a |
@orthoxerox No, we do not use the platform target to change the language we're accepting. We have a language switch for that. We're not going to publish a language specification that says that different versions of "the platform" have different required diagnostics. @KrisVandermotten Of course we could make the new switch one of the expression forms allowed in an expression-statement, but I suspect you would not find it as useful as you think. Do you find yourself wishing the @HaloFour I do not believe that the existence of an expression form of switch would reduce the value of a statement form at all. |
@gafter I completely agree. What I meant was that it's pretty unlikely to see To note, I'm not advocating abandoning the changes to |
What use will be |
@HaloFour I don't think you would find a new match expression very useful as a statement-expression. You would not be able to throw, return, try-finally, or do any other statementy things in it. That's why I think we need to both extend the switch statement and add an expression form. The topic of this issue is how to do the former in the most useful way with the least disruption. @dsaf Are you suggesting the (existing) switch statement isn't useful? 😉 |
@gafter No, I am thinking that existing |
@dsaf I'm hoping we can extend the existing |
@gafter Well I assume that depends on the exact syntax. Granted the string result = match (exp) {
case Foo(x) => "foo",
case Bar(x) => "bar",
case Baz(x) => {
SomeClass c = new SomeClass();
try {
c.SomeMethod();
return "baz";
}
finally {
c.Close();
}
}
default => throw InvalidOperationException()
}; This probably belongs in another proposal, though. If an expression match (exp) {
case Foo(x) => Console.WriteLine("foo"),
case Bar(x) => Console.WriteLine("bar")
}; |
@gafter Understood.
What if another level of inspection severity is added? It could be intended either to address this sort of situations specifically or for general use. Additionally there could be several pre-configured IDE/compiler profiles e.g. "Max Compatibility" and "Max Strictness". The latter would treat hints as errors and allow breaking changes in future. |
Yes, and it also might be confusing that the branches are restricted to expressions, and the match is required to be complete (thus your example is probably an error).
You can configure individual warnings something like this in VS2015. But you can't turn off errors that are required by the language specification because - well, they are required in order for the compiler to be implementing the C# programming language. |
Although extending Given the diagnostics and code fixes Roslyn now allows I also think you could make it pretty easy for people to convert their
|
@gafter Yes, I realize it would be an error. I guess I'd need a In an expression form each branch would be required to return the same type anyway, right? So would it be that confusing if that type could be |
@MgSam If we added a statement form of @HaloFour In a |
Let's imagine you go for the breaking change, wouldn't it mean that the following should start giving an error as well - for parity? if (true)
{
}
else //C#6 - OK, C#7 - error, impossible to reach given the actual value.
{
} |
@gafter I strongly disagree. As a smart man recently said in a comment on a different thread about the same topic:
This comment applies equally well to a new statement form of match without the need for It would be a very poor choice to constrain new features just so that it slightly reduces the learning curve. If you always were limited by such constraints, lambda expressions would require parameters to be explicitly typed so that they look more like methods/anonymous delegates. I think we can all agree that we're glad that didn't happen. And again, you have the ability to have much better tooling with code assistance now. If someone accidentally writes |
I agree, a statement form of As mentioned before perhaps this conversation belongs in a different thread/proposal. |
@dsaf No, the specification for the @MgSam The differences you suggest for a |
@gafter: I'm not sure I get your point. What is the problem with automatically refactoring between the old |
What code? My words you quoted are intended to require an error when you try to match an expression like |
@gafter I would say not having to write lots of extra verbiage certainly increases expressiveness. Also, why would it be an on-going pain point? A new keyword would indicate that there is a new concept people have to learn. Again, as you said in your own words; once you learn the new syntax it is no longer a pain point because presumably you don't have to keep re-learning it over and over again. I think the situation is highly analogous to the introduction of lambdas. I'm certain they co-existed with anonymous delegates in the same codebases and probably confused people at first and caused bugs when they were misused. Did that mean that the feature was a mistake to add? In fact, I rarely ever see |
@axel-habermaier Adding support for pattern matching hasn't yet proven any need for a new statement form. The corner cases that we've run into integrating them into |
No, starting in C# 6 (VS2015) it does not do that any longer. |
We had really hoped that we could extend the existing
switch
statement to support pattern-matching (#206) in an upward compatible way. Most of the potential semantic mismatches between the existing switch statement and its extension to pattern matching can be erased, in the sense that the pattern-matching version can reasonably be defined to match the existing switch statement syntax and semantics precisely. However there is one issue I’m not sure what to do about.In a switch statement today
the expression is required to be of one of a particular set of types (sbyte, byte, short, ushort, int, uint, long, ulong, bool, char, string, or an enum type) or its corresponding nullable type. Alternately it may be of a user-defined type that can be uniquely converted to one of these types via a user-defined implicit conversion.
If we remove this user-defined conversion condition completely, existing code that relies on the user-defined conversion will break.
If we do not remove this condition, types that have such a conversion won’t really be capable of being the governing expression of a switch statement with any nontrivial pattern-matching semantics, because the expression would immediately be converted to one of these governing types. And it would not be very intuitive that simply adding the conversion to a type breaks your ability to do pattern-matching (via
switch
) on values of that type.I'm not sure what to do about this, exactly, other than possibly leave the existing
switch
alone and instead invent a newmatch
statement just for use with pattern-matching./cc @AnthonyDGreen @terrajobst @jaredpar @Pilchie @ljw1004 @MadsTorgersen @mattwar @gafter @stephentoub @vancem @semihokur
The text was updated successfully, but these errors were encountered: