-
Notifications
You must be signed in to change notification settings - Fork 213
Allow switch without scrutinee and patterns in cases #3457
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
Comments
So if a switch has no expression, the patterns are all reduced to the when clause expressions (no value matches no pattern 100% of the time), or to a This would apply to both expression and statment switches: int compare(int a, int b) => switch {
a < b => -1, // no 'pattern when'
a == b => 0,
_ => 1,
}; and int compare(int a, int b) {
switch {
case a < b: return -1; // no 'pattern when'
case a == b: return 0;
default: return 1;
}
} Not unreasonable. (Can we remove |
I love it. |
Thinking about it more. Maybe this should be an if {
a < b => -1,
a == b => 0,
else => 1,
} Or maybe not, leave It would also pretty much assume an expression- switch (cond) {
true => thenExpr,
_ => elseExpr,
} today, that's not a big leap. |
I particularly like the
Also, I agree that |
This is about choosing the first of a list of possible cases whose boolean condition evaluates to true. if (isAvailableX()) action = doAndReturnX();
else if (isAvailableY()) action = doAndReturnY();
else if (isAvailableZ()) action = doAndReturnZ(); or even if (isAvailableX()) action = doAndReturnX();
else
if (isAvailableY()) action = doAndReturnY();
else
if (isAvailableZ()) action = doAndReturnZ(); These are all valid Dart syntaxes, just not ones supported by the formatter. If feel like a bunch of the syntax requests we get are for things that wouldn't be necessary if the formatter was less strict about putting things on the same line. void oneLineFunction(arg) { expressionStatement; } we wouldn't have needed to special-case void oneLineFunction(arg) => expressionStatement; and if we allowed That said, I can see why: if {
isAvailableX(): action = doAndReturnX();
isAvailableY(): action = doAndReturnY();
isAvailableZ(): action = doAndReturnZ();
} might read even better. The if (isAvailableX()) action = doAndReturnX();
| (isAvailableY()) action = doAndReturnY();
| (isAvailableZ()) action = doAndReturnZ(); (I know that'l never parse, but it does look nice.) |
I do agree.
Ifs aren't allowed to be used within an expression context as switch does. You cannot: final value = if (isAvailableA()) getA()
else if (isAvailableB()) getB()
else null; Even if it did, we are still left with the verbose
This issue is not just about having a clever way of "choosing the first of a list of possible cases whose boolean condition evaluates to true" but also being able to do that within an expression context. Let's say we have the following syntaxes: if (isAvailableX()) action = doAndReturnX();
else if (isAvailableY()) action = doAndReturnY();
else if (isAvailableZ()) action = doAndReturnZ(); if (isAvailableX()) action = doAndReturnX();
else
if (isAvailableY()) action = doAndReturnY();
else
if (isAvailableZ()) action = doAndReturnZ(); if {
isAvailableX(): action = doAndReturnX();
isAvailableY(): action = doAndReturnY();
isAvailableZ(): action = doAndReturnZ();
} if (isAvailableX()) action = doAndReturnX();
| (isAvailableY()) action = doAndReturnY();
| (isAvailableZ()) action = doAndReturnZ(); All of them need to write The key is to be able to do a clever if/else within an expression context (e.g one-line function, variable assignments). |
There's an option to reuse the syntax of conditional expression by adding some decorations, with a different formatting var x = if {
: cond1 ? expr
: cond2 ? expr
: expr
} This syntax works and formats nicely with or without the assignment. (The first colon is added for beauty) |
Don't need the action =
isAvailableX() ? doAndReturnX() :
isAvailableY() ? doAndReturnY() :
isAvailableZ() ? doAndReturnZ() :
null; We already have the syntax for condition chains as expressions. The biggest issue is the formatter not recognizing a chain of theses as something that should be laid out at the same indentation level. |
Suppose the formatter somehow acquires the ability to nicely format this expression. This will solve the problem of if- expressions syntax, right? if {
isAvailableX(): action = doAndReturnX();
isAvailableY(): action = doAndReturnY();
isAvailableZ(): action = doAndReturnZ();
} can be encoded like this: isAvailableX() ? action = doAndReturnX() :
isAvailableY() ? action = doAndReturnY() :
isAvailableZ() ? action = doAndReturnZ() :
doNothing; // we can use null here, too The problem is that the syntax forces us to write an extra line which has no meaning. An obvious solution is to make the last colon optional (in this context), so we can write it simply as isAvailableX() ? action = doAndReturnX() :
isAvailableY() ? action = doAndReturnY() :
isAvailableZ() ? action = doAndReturnZ() ; Does this solve the problem? Or it will need some extra decor - e.g. in the form of enclosing |
My worry here is that we may be chasing brevity, without actually achieving readability. The examples here show things that are similar. They have similar structures because they do similar things in similar ways. The goal of code formatting is to expose such similarities instead of hiding them. Adding new syntax to get around that seems like treating a self-inflicted wound. We could just allow code to be exempted from automatic formatting, and then the author can do whatever brings out the similarities, even if it's as un-traditional as: if (isAvailableX()) action = doAndReturnX();
else
if (isAvailableY()) action = doAndReturnY();
else
if (isAvailableZ()) action = doAndReturnZ(); Maybe there will be a handful of non-standard patterns that people end up preferring for specific code patterns, and maybe the formatter can eventually learn to recognize and support them. But we shouldn't introduce new syntax just to get around the formatter. It's much easier to change or ignore the formatter then. |
Changing the formatter to allow something like this: final value =
isAvailableA() ? getA() :
isAvailableB() ? getB() :
isAvailableC() ? getC() : null; is going to solve the issue of not having a clever way of "choosing the first of a list of possible cases whose boolean condition evaluates to true". But I think the best output of this issue would be to have a more unified flow control for multiple choices, like we have in Kotlin with the What about integrating the runtime evaluation with all current compile-time supported features of the TestLevel level;
// Scrutinee optional. So `switch { ... }` is also valid.
final result = switch (level) {
// Currently throws 'The binary operator is is not supported as a constant pattern.' (it's a non-constant expression)
// Only supported through pattern-matching `FirstSpecialCaseLevel()` (Less readable but possible to resolve as constant).
is FirstSpecialCaseLevel => ...,
// Currently throws 'The relational pattern expression must be a constant.'
// Also, If no scrutinee is given, throws a compile-error, since there are no value to compare `< minimumAllowed()` with.
< minimumAllowed() => ...,
> maximumAllowed() => ...,
tooHigh() => ...,
tooLow() => ...,
withinHealthLevels() => ...,
is SpecialCaseLevel => ...,
AnotherSpecialCaseLevel(:final field) when ... => ...,
anotherCheck() when lastCheck() => ...,
_ => ...,
}; I wonder if this may have implications because we will be mixing different kinds of expressions ("non-constant" and "constant") within the same "scope" (I've no internal SDK knowledge so feel free to correct misplaced terms). |
I think we are chasing not brevity for its own sake, but rather trying to come up with visually recognizable patterns, and those tend to be relatively short. Imagine that isAvailableX() and others are not there, and instead we have to write them as inline expressions, each of different size. Then good formatting would be a challenge even for an author. late isAvailableX = ...long expression
late isAvailableY = ...
late isAvailableZ = ... The goal is to make every |
Currently the only way I can do that is by: final isAvailableX = (() {
// ... long expression with ifs and explicit returns
})();
// instead of
final isAvailableX = // ... a bunch of mixed '&&', '||' and '()' with unrelated subjects. Although it would be a great improvement, I think it's outside of the scope of the current issue (at least from what I thought initially, but I would love to see these inline expressions as well). final isAvailableX => {
if (check1) return result1;
if (check2) return result2;
return resultDefault;
}; |
Assuming you don't really need a function , you can write the condition as final isAvailableX =
check1 ? result1 :
check2 ? result2 :
resultDefault; If you do need a function, you can write it in a similar manner: isAvailableX() =>
check1 ? result1 :
check2 ? result2 :
resultDefault; The chain of conditional expressions is an exact equivalent to a switch without the scrutinee (the topic of current thread) |
It's clear by that point that this kind of formatting for a conditional operator won't be possible in general (at least, not automatically) isAvailableX() =>
check1 ? result1 :
check2 ? result2 :
resultDefault; But I found more than a few cases where "switch without scrutinee" would make the program cleaner. This request belongs to the category of "small and useful features". It's very small. And very useful. isAvailableX() {
return switch {
check1 => result1,
check2 => result2,
_ => resultDefault,
}
} |
Recently Dart 3 introduced switch expressions, although they facilitate type-casting-like flows through pattern matching, we still can't use flows where there is no compile-time pattern to match but a runtime expression to evaluate:
Using switch expression we are forced to give the switch the runtime object we want to work with along with the compile-time patterns:
In Kotlin we can simply:
A similar approach in Dart would be:
The text was updated successfully, but these errors were encountered: