I propose adding Case-Ranges to the expression switch statement.
Case-Ranges allows to specify a range of consecutive values in a single case clause, like this:
case low ... high :
An example:
// rune or byte
switch r {
case 'a' ... 'z':
case 'A' ... 'Z':
case '1' ... '9':
}
// numbers
switch n {
case 1...5: // error
case 1 ... 9: // ok
}
// underlying type can be compared using the "<" ">" operators.
switch t {
case time.Friday:
case time.Saturday:
case time.Sunday ... time.Thursday:
}
Go's switch statement is really powerful, and I really enjoy using it. however, I think we can
improve it and make it more robust than it is.
Case-Ranges simplifies the code and makes it more readable than it is today.
An example of using the expression-switch statement.
switch r {
case '.':
case '[', '{':
case ']', '}':
case '"', '`':
case ' ', '\t', '\r', '\n':
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
default:
switch {
case 'a' <= r && r <= 'z':
case 'A' <= r && r <= 'Z':
}
}
We can make the code more readable, and remove the expression from the switch statment.
switch {
case r == '.':
case r == '[' || r == '{':
case r == ']' || r == '}':
case r == '"' || r == '`':
case '0' <= r && r <= '9':
case 'a' <= r && r <= 'z':
case 'A' <= r && r <= 'Z':
case r == ' ' || r == '\t' || r == '\r' || r == '\n':
}
With Case-Ranges it will be much more readable, simplified, and _fun_.
switch r {
case '.':
case '[', '{':
case ']', '}':
case '"', '`':
case 'a' ... 'z':
case 'A' ... 'Z':
case '0' ... '9':
case ' ', '\t', '\r', '\n':
}
No impact on existing code.
What makes a type Rangeable? How does that work?
While reading your comment, I figured out that this term is confusing. so I've changed the proposal.
I'm expecting that a case-ranges like this: 1 ... 9, will be translate logically to something like
this: 0 <= r && r <= 9.
So I get that you want to have syntax like this:
case a ... b:
Do a and b have to be constants? Or merely constant expressions? Or do you want to allow a and b to be arbitrary expressions?
What happens if a and b are floats unordered with respect to the value we switch over?
In my opinion, the variadic parameter syntax is discordant as an infix operator. I understand that "..." is used within the metasyntax notation which defines Go for the exact purpose of describing ranges, but within the language it acts as a value handling modifier and lacks a harmony with it's use as a simple range indicator.
I have no strong feelings about an alternative, but would suggest: case a .. b :.
@daved A new token would be a bit confusing and possibly compatibility-breaking, now people have to distinguish .. and ... and I promise you they would get it wrong every single time.
@fuzxxl A "range OR variadic" operator/special syntax would be sloppy and could muddle communication. They should be different, because they have fundamentally different meanings despite the small logical similarity. Please, consider what the variadic parameter operator is doing relative to a basic indication of range.
Do a and b have to be constants? Or merely constant expressions?
no need to be constants.
if we'll take this example:
switch y {
case x ... z:
}
we are expecting that x <= y and y <= z are valid comparisons.
Or do you want to allow a and b to be arbitrary expressions
no. if you want to use an arbitrary expression, use switch without an expression. same as today.
I have to say I've missed this in a few hot paths where using a naked switch just isn't as efficient. However, in those very few cases, manually listing the items is still an option, and what I'd go for if performance is the priority. If I remember correctly, the compiler does recognize ranges if you list them out explicitly.
If they don't need to be constant expressions, this is a bit tricky to implement because the compiler doesn't know if x < z in advance. Plus, how does your proposal deal with overlapping ranges?
If they are not necessarily constants, what else the compiler need to know about them except their type?
here's a quote from the Go spec:
For each case expression x and the value t of the switch expression, x == t must be a valid comparison.
This requirement is checked on the compile time. I'm expecting that the checking for the Case-Ranges also will be in the compile time.
Plus, how does your proposal deal with overlapping ranges?
If your case clause declared with constants, I'm expecting from the compiler to give an informative error.
for example, if you trying to compile something like this case 'z' ... 'a' with gcc, you'll get a warning that says: "empty case range specified".
If low and high are arbitrary values that's a different story 🤔, we'll handle that like this: low <= x && x <= high.
Thank you for the activity guys! these are really good points. I must say that I didn't even think about them at the first.
This is entirely syntactic sugar. In a switch on the expression x,
case a ... b:
is equivalent to
case a <= x && x <= b:
We would want to define various additional conditions for the case where a and b are constants.
We would have to explain what happens when a > b, and the answer is likely to be different depending on whether a and/or b are constants or not. For example, if they are not constants, do we want to panic at run time? Probably not. But we probably do want a compilation error in that case.
At present switches translate into code using ==, so the type has to be comparable. When using a range, the type had to be ordered. Or, we would could change the sugar to not use the expression above, but to somehow expand into a list of values compared using ==.
All in all this seems to add a fair amount of complexity to the spec, all to avoid writing a <= x && x <= b. It can be a nice feature but overall it doesn't seem worth it.
Closing.
I was hoping for some syntax such as
switch c {
case 1...8, 11...16, 21...26, 31...36, 61...66:
foo()
case 0:
bar()
}
Most helpful comment
This is entirely syntactic sugar. In a switch on the expression
x,case a ... b:
is equivalent to
case a <= x && x <= b:
We would want to define various additional conditions for the case where
aandbare constants.We would have to explain what happens when
a>b, and the answer is likely to be different depending on whetheraand/orbare constants or not. For example, if they are not constants, do we want to panic at run time? Probably not. But we probably do want a compilation error in that case.At present switches translate into code using
==, so the type has to be comparable. When using a range, the type had to be ordered. Or, we would could change the sugar to not use the expression above, but to somehow expand into a list of values compared using==.All in all this seems to add a fair amount of complexity to the spec, all to avoid writing
a <= x && x <= b. It can be a nice feature but overall it doesn't seem worth it.Closing.