Support runtime expression evaluation in case clauses. #2711
Replies: 27 comments
-
I'm not sure I understand what you're proposing. In the example code, some |
Beta Was this translation helpful? Give feedback.
-
AIUI that |
Beta Was this translation helpful? Give feedback.
-
However, it's important to understand that you'd be making deconstruction in switch statements significantly more verbose if you go down this path. Since each var tup = (a, b, c);
switch (tup)
{
case t if t is var (x, y, z):
...
} (You can't generally do |
Beta Was this translation helpful? Give feedback.
-
@svick @alexrp - The example is not 100% clean it was put together to show the principle. None of the cases there though are type, they are all (ultimately) a Boolean, a case need not have an expression referring to the switch value. The principle if that each There are two distinct forms and my example fails to convey that, the switch may either have an expression or not - these are the two forms allowed (in the PL/I language that is)
// Form 1 - switch evaluates an expression, equality with a case is sought
private static void ShowCollectionInformation(object coll, object other)
{
switch (coll.GetType())
{
case typeof(Array):
Console.WriteLine($"An array with {(Array)col.Length} elements.");
break;
case typeof(IEnumerable<int>):
Console.WriteLine($"Average: {(IEnumerable<int>)coll.Average(s => s)}");
break;
case typeof(IList):
Console.WriteLine($"{(IList)coll.Count} items");
break;
case typeof(IEnumerable):
string result = "";
foreach (var e in (IEnumerable)coll)
result += "${e} ";
Console.WriteLine(result);
break;
case other.GetType():
// Do nothing for objects of same type
break;
default:
Console.WriteLine($"A instance of type {coll.GetType().Name}");
break;
}
}
// Form 2 - switch has no associated expression, a true case expression is sought
private static void ShowCollectionInformation(object coll, object other)
{
switch ()
{
case (coll == null && other == null):
Console.WriteLine("Both null");
break;
case (coll == null && other != null) || (coll != null && other == null):
Console.WriteLine($"One of them is null");
break;
case coll.ToString.Length() > other.ToString.Length():
Console.WriteLine("coll has longer text.");
break;
case other.ToString.Length() > coll.ToString.Length():
Console.WriteLine("other has longer text.");
break;
case coll.GetType() == other.GetType():
// Do nothing if same type
break;
default:
Console.WriteLine($"A instance of type {coll.GetType().Name}");
break;
}
} |
Beta Was this translation helpful? Give feedback.
-
This might be an argument for why pattern matching should never have hijacked the |
Beta Was this translation helpful? Give feedback.
-
@Korporal Your Form 1 looks like a worse version of the existing type pattern: private static void ShowCollectionInformation(object coll, object other)
{
switch (coll)
{
case Array array:
Console.WriteLine($"An array with {array.Length} elements.");
break;
case IEnumerable<int> ints:
Console.WriteLine($"Average: {ints.Average(s => s)}");
break;
case IList list:
Console.WriteLine($"{list.Count} items");
break;
case IEnumerable enumerable:
string result = "";
foreach (var e in enumerable)
result += "${e} ";
Console.WriteLine(result);
break;
case var _ when coll.GetType() == other.GetType():
// Do nothing for objects of same type
break;
default:
Console.WriteLine($"A instance of type {coll.GetType().Name}");
break;
}
} The one dynamic test is more verbose, but the static tests are shorter and safer (you don't have to repeat the type, so there is no chance of mismatch). As for Form 2, I don't see how is that better than an Note that I'm not against dynamic tests in pattern matching per se, but I do think you need a much better justification. |
Beta Was this translation helpful? Give feedback.
-
If used to replace the type pattern yes - my example was showing how the more general switch could be used to examine types, that's all. The core of the suggestion is the support for runtime expression evaluation in case clauses, whether you'd leverage that for types is your choice and we probably would not. So that's all I'm proposing we extend switch so that case values can be runtime expressions rather than compile time.
In which case the existing primitive switch with constant cases values is no better than an if-else sequence, that too is certainly more verbose. You seem to be arguing that switch itself has no values and if-else is all we really need. I do not see how a switch that replaces this: if (value == 1)
// stuff
else if (value == 2)
// stuff
else if (value == 3)
// stuff
else
// default stuff with a switch is deemed by you less verbose yet replacing this: if (value == current_value)
// stuff
else if (value > current_value)
// stuff
else if (value > current_value && << max_value)
// stuff
else
// default stuff with a switch is deemed more verbose? The switch code in each case would be: switch (value)
case 1:
// stuff;
break;
case 2:
// stuff;
break;
case 3:
// stuff;
break;
default:
// default stuff; and switch ()
case value == current_value:
// stuff;
break;
case value > current_value:
// stuff;
break;
case value > current_value && value < max_value
// stuff;
break;
default:
// default stuff; The latter - being more general - can do all that the existing switch can and more. This is all that the PL/I select is, an alternative syntax for any if-then-else ladder not just for a restricted subset of such ladders where the if-then-else ladder expressions compares a single expression value to a set of constants for equality. Yes the syntax does become more verbose in the case of a type pattern In an ideal world But that's history it is what it is and extending Insisting that C# mimic the feeble C definition of a switch and disregarding how it was done in almost every other programming language shows me how deeply hampered C# is by that choice of base language and grammar. |
Beta Was this translation helpful? Give feedback.
-
@Korporal
I don't need to argue for that, That being said, I think even C-style
These reasons don't apply to your proposed |
Beta Was this translation helpful? Give feedback.
-
This is not true. 'switch' in C# has the value of encoding the
by this logic, we could just stick with gotos. After all, nearly all language constructs can be written with them. But we like having higher level concepts that we can use instead as they make the code clearer. Yes, people can interchange them (and they often do!), but that doesn't mean because they can be swapped around that they don't serve a purpose. |
Beta Was this translation helpful? Give feedback.
-
You can effectively do this with the switch (0)
{
case var _ when value == current_value:
// stuff;
break;
case var _ when value > current_value:
// stuff;
break;
case var _ when value > current_value && value < max_value
// stuff;
break;
case var _:
// default stuff;
} If you want to write in that fashion, feel free. But i don't think the language is going to work particularly hard to make this any easier. |
Beta Was this translation helpful? Give feedback.
-
As mentioned in your other thread: This is not PL/1#. If you want that, you'd likely have a better chance just building it yourself. This is C# and C# is going to continue it's development in this way for the foreseeable future. Perhaps this just isn't the right language for you? You might want to consider many of the others out there. VB, as you mentioned, behaves in the manner you like here. It has full access to the wealth of functionality on the .net platform if htat matters to you. If you'd prefer a more functional language, F# might be right up your alley as well. C# has no goals for 'switch' to operate like PL1's 'select'. It does not look at PL1 as a valuable language to take direction from. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi - Why state the obvious? I know it isn't PL/I - I referred to that because it's However you are quite right the Having said that look at the messy syntax one must use to do as you suggest:
wouldn't it have been better to let us write:
when we want it to act like The language's syntax does seem to be getting clumsier and more idiosyncratic as MS add more features to it, I doubt you'd design it this way again if you wanted a language to support all that it currently does. |
Beta Was this translation helpful? Give feedback.
-
It is confined to that node. |
Beta Was this translation helpful? Give feedback.
-
Not that i can see, no. It's a non goal to make all c# constructs ideal for all scenarios. In particular, there is no goal to make
Making it support all these new scenarios that are out of scope would certainly not help here.
Of course not. Welcome to language design. You have that flexibility in greenfield projects. However, with languages that are 20 years old **, there are a different set of constraints you have to work with. If this is unappealing to you, i suggest you seek out other languages. Languages pick and choose teh areas they want to excel at and focus their efforts and interests in. When those are areas you consistently do not seem to similarly value, you should likely question if this is the right language for you to be using. Perhaps you should seek out one that shares your syntactic preferences? Perhaps PL/I for .Net would be more up your alley? -- ** That's older than some of the people working on it now. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi - Of course PL/I for .Net is absurd, I have no interest in working with that language, but I do think that language and numerous others have certain things to offer if one is designing a new language, There has been an obsession almost with the feeble C grammar and I think this is a bad thing. I'm sure it would be an interesting academic project to devise a brand new grammar that can support all that C# supports (in terms of functionality) yet not have to pay homage to C. Rust, Java, C#, Swift etc all in some way or other betray a C-like syntax. I just seems to me that any new language, particularly OO subconsciously presumes a C-like synatx and just adds stuff on top of that. Anyway, this isn't the place for this discussion. |
Beta Was this translation helpful? Give feedback.
-
You're the one who seems to keep bringing it up over and over again. There does appear to be an unhealthy obsession from where i'm sitting... |
Beta Was this translation helpful? Give feedback.
-
It's not subconscious. You can talk to the language designers here and they'll tell you their explicit reasons for this sort of thing. Heck, we've discussed it before at length. I'm not really certain why this has become such a sticking point for you. However, to me, it's starting to look very unhealthy. Honestly, C# just may not be the language for you. Certainly, consistently tilting at windmills over the syntax isn't going to do much (except maybe be cathartic for you). As usual, I recommend discussions like this go to a more conducive location (like gitter.im or discord). You'll find healthy communities there willing to just discuss random stuff like this. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi - The grammar sucks, so of course that will come up frequently when discussing options and feasibility of shoehorning new features into it. |
Beta Was this translation helpful? Give feedback.
-
This is a non-goal of C#. As mentioned already, there is an existing construct which is viewed as more than sufficient for the use case you have brought up. So there's no one (except you) pushing to make this other construct supplant it. There's no need to shoehorn anything here.
As mentioned already, we're not going to re-litigate the past. The grammar was chosen intentionally with the understanding of the impact that would have on the project. That decision isn't going to change.
If you continue railing against this particular decision then you're going to find yourself continually disappointed where there is no interest in making these changes. If that's ok with you, so be it. However, if you actually want to see changes happen, you need to understand the perspective of the team and what is valued (and was is not valued) by the LDM. Continued attempts to ignore that, and continually push for these sorts of things won't really go anywhere. |
Beta Was this translation helpful? Give feedback.
-
There are other languages out there. You're welcome to use ones that have a grammar that is similar to the one you would like. In general, trying to get languages to really change their grammar decisions is going to be very difficult to do. And, in specific, for C# it's going to be a massive uphill battle. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi - I've stated the grammar sucks - you either agree or disagree, I am not advocating we change the C# grammar, yes I sometimes lament the choices that were made when the design was started but that's not the same as advocating we change the C# grammar. Having said that when discussing new language features and enhancements the grammar will come up from time to time. As I already stated a new .Net OO language with a superior and well designed grammar would be an interesting academic exercise, sooner or later updating C# will become very difficult due to the inflexible and fundamentally weak grammar, perhaps when that day arrives you'll see the light! |
Beta Was this translation helpful? Give feedback.
-
You're welcome to take that discussion to a better venue. csharplang is not the right forum for waxing philosophical on this. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi - Nor is it the right forum for continually striving to have the last word. |
Beta Was this translation helpful? Give feedback.
-
The fact that the cases are all constants permits the language to require that the compiler do a tremendous amount of static analysis (i.e. determining when you've already tested for a value, and for the switch expression determining when it covers all cases), and to generate a very efficient indexed jump to implement the switch. The generalized construct has neither semantic or performance advantage. You might as well just use a series of if-then-elses. |
Beta Was this translation helpful? Give feedback.
-
I get a same question. I think |
Beta Was this translation helpful? Give feedback.
-
I want use it by this: return typeof (T) switch {
typeof (string) => "a",
typeof (int) => "b",
typeof (_MyClass1) => "c",
typeof (long) => "d",
typeof (_MyClass2) => "e",
_ => "f",
}; now only: return typeof (T) switch {
var _t when _t == typeof (string) => "a",
var _t when _t == typeof (int) => "b",
var _t when _t == typeof (_MyClass1) => "c",
var _t when _t == typeof (long) => "d",
var _t when _t == typeof (_MyClass2) => "e",
_ => "f",
}; |
Beta Was this translation helpful? Give feedback.
-
Y'know, I'd totally be in favor of allowing |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I touched upon this here and the more I have thought about it the more surprised I am that it hasn't been considered (at least I don't think it has).
Currently case 'values' are constants, compile time constants.
This design has been copied from it's roots in the C language (again this is another example of why C/C++/Java have hindered the ongoing development of C#).
The documentation for
switch
says:If only that were true!
In fact with a true if-then-else ladder we are not forced to do equality checks against constants, we can do much more than that.
So is there merit in considering an enhancement to the C# language here? This would be backward compatible.
I learned C by developing a compiler for PL/I in C, I knew PL/I very well and over time my knowledge of C ramped and this work gave me a good insight into both languages.
When learning C I was struck by the very feeble nature of
switch
compared to the richerselect
found in PL/I (this predates SQL and is nothing to do with that).C# supports these so-called switch patterns: constant pattern and type pattern.
But if it supported the 'expression pattern' it could inherently support both of the above, there'd be just one pattern.
The 'type pattern' could then simply be (I've added a little to the example from the doc page)
This is a much more general and powerful construct in which doing processing based upon type is not elevated to some special pattern, but is trivial and truly comparable to the generality we have with if-then-else ladders.
I'm not suggesting a mere alternative to the 'type pattern' but a generalization that dispenses with the need to even have a special pattern for types, it becomes general enough to do types without issue and much much more.
The idea is to allow any valid expression in a
case
clause, if it evaluates to true then we do that block.Beta Was this translation helpful? Give feedback.
All reactions