Skip to content
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

proposal: spec: add case-ranges to the expression switch statement #21218

Closed
a8m opened this issue Jul 29, 2017 · 12 comments
Closed

proposal: spec: add case-ranges to the expression switch statement #21218

a8m opened this issue Jul 29, 2017 · 12 comments
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@a8m
Copy link
Contributor

a8m commented Jul 29, 2017

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.

Current alternatives compared to the new spec

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':
}

Impact

No impact on existing code.

@gopherbot gopherbot added this to the Proposal milestone Jul 29, 2017
@btracey
Copy link
Contributor

btracey commented Jul 29, 2017

What makes a type Rangeable? How does that work?

@bradfitz bradfitz added v2 A language change or incompatible library change LanguageChange labels Jul 29, 2017
@a8m
Copy link
Contributor Author

a8m commented Jul 29, 2017

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.

@clausecker
Copy link

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?

@daved
Copy link

daved commented Jul 29, 2017

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 :.

@clausecker
Copy link

@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.

@daved
Copy link

daved commented Jul 29, 2017

@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.

@a8m
Copy link
Contributor Author

a8m commented Jul 30, 2017

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.

@mvdan
Copy link
Member

mvdan commented Jul 30, 2017

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.

@clausecker
Copy link

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?

@a8m
Copy link
Contributor Author

a8m commented Jul 30, 2017

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.

@ianlancetaylor
Copy link
Contributor

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.

@catenacyber
Copy link
Contributor

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()
}

@golang golang locked and limited conversation to collaborators Feb 1, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

9 participants