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: allow comparison operators in switch case labels C# (VS 16.8, .NET 5) #812

Open
gafter opened this issue Aug 10, 2017 · 26 comments
Assignees
Labels
Design Review Feature Request Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion
Milestone

Comments

@gafter
Copy link
Member

gafter commented Aug 10, 2017

@JFMous commented on Sat May 14 2016

It would be nice if the case labels in switch statement supported comparison operators, much like the Select Case statement in Visual Basic does.

Now, there are only two options for the switch labels:

switch-label:
case constant-expression :
default :

This could be extended to:

case [< | > | <= | >=] constant-expression [ to ] constant-expression :
default :

So you could write code as in this example:

int iq = DoIqTest();

switch (iq)
{
    case <= 69:
        ProcedureExtremelyLow();
        break;
    case 70 to 79:
        BorderlineProcedure();
        break;
    case 80 to 89:
        LowAverageProcedure();
        break;
    case 90 to 99:
    case 101 to 109:
        AverageProcedure();
        break;
    case 100:
        ExactlyMedianProcedure();
        break;
    case 110 to 119:
        HighAverageProcedure();
        break;
    case 120 to 129:
         SuperiorProcedure();
         break;
    case >= 130:
        VerySuperiorProcedure();
        break;
}

The 'to' keyword would be used to specify a range. In the statement switch (value), case x to y: would be equivalent to the boolean expression value >= x && value <= y


@HaloFour commented on Sat May 14 2016

Case guards in pattern matching #206 will open this up to allowing for arbitrary conditions:

int iq = DoIqTest();

switch (iq)
{
    case * when iq <= 69:
        ProcedureExtremelyLow();
        break;
    case * when iq <= 79:
        BorderlineProcedure();
        break;
    case * when iq <= 89
        LowAverageProcedure();
        break;
    case * when iq == 100:
        ExactlyMedianProcedure();
        break;
    case * when iq <= 109:
        AverageProcedure();
        break;
    case * when iq <= 119:
        HighAverageProcedure();
        break;
    case * when iq <= 129:
         SuperiorProcedure();
         break;
    default:
        VerySuperiorProcedure();
        break;
}

The wildcard pattern would match on anything but then the case guard would contain the specific conditions. They would be evaluated in lexical order.


@alrz commented on Sun May 15 2016

would it make sense to make case optional or perhaps switch expression?

switch {
  when id <= 69: ... break;
  ...
}

might be preferrable over if else.


@bondsbw commented on Mon May 16 2016

@alrz I like that somewhat, feels like a reasonable simplification.

But I feel this may be even more useful in pattern matching. In match expressions, the roughly equivalent form might be:

var x = match
{
    when iq <= 69: ProcedureExtremelyLow(),
    when iq <= 79: BorderlineProcedure(),
    when iq <= 89: LowAverageProcedure(),
    when iq <= 99 || (iq <= 109 && iq >= 101): AverageProcedure(),
    when iq == 100: ExactlyMedianProcedure(),
    when iq <= 119: HighAverageProcedure(),
    when iq <= 129: SuperiorProcedure(),
    default: VerySuperiorProcedure()
};

I like the possibilities but if iq is always the comparison, it really feels like it should be the subject of the match expression. Something like the following feels more like pattern matching (although it would require a bit of rethinking what a relational expression is):

var x = iq match
{
    when <= 69: ProcedureExtremelyLow(),
    when <= 79: BorderlineProcedure(),
    when <= 89: LowAverageProcedure(),
    when == 100: ExactlyMedianProcedure(),
    when <= 109: AverageProcedure(),
    when <= 119: HighAverageProcedure(),
    when <= 129: SuperiorProcedure(),
    default: VerySuperiorProcedure()
};

And I'm not sure I like changing

when iq <= 99 || (iq <= 109 && iq >= 101):

into something like

when <= 99 || (<= 109 && >= 101):

@Unknown6656 commented on Mon May 23 2016

@bondsbw, @JFMous : I would like to see something like the following implemented with the match-pattern:

Func<int, string> func1, func2, func3, ...;

int iq = ...;
string result = iq match
{
    when <= 100: func1,
    when == 100: func2,
    when >= 100: func3,
    // ....
    default: funcx()
}(iq);

Meaning, the possibility to use the match-statement inside of expressions.


@HaloFour commented on Mon May 23 2016

@Unknown6656 The proposal is that match is an expression, so of course you'd be able to use it within other expressions. You can think of it like a ternary op on steroids.

As for your specific example, there have been no proposed range patterns. Nor has there been a proposal to allow for only case guards with an implied wildcard pattern. So as of now your example would be:

string result = iq match (
    case * when iq < 100: func1,
    case * when iq == 100: func2,
    case * when iq > 100: func3,
    case *: funcx
)(iq);
@DavidArno
Copy link

Rather than implementing this limited feature, could the team just finish implementing pattern matching, including match expressions and get that released, please?

@bondsbw
Copy link

bondsbw commented Aug 11, 2017

@DavidArno Haha there is a thing called "priorities".

@DavidArno
Copy link

@bondsbw,

Exactly. They should prioritise finishing the half-complete pattern matching feature over adding new features like nullable reference types and those interface thingies 😉

@iam3yal
Copy link
Contributor

iam3yal commented Aug 11, 2017

@DavidArno I agree that match expression would be awesome to have sooner than later but don't know if this is the place to discuss priorities, it's a reasonable suggestion, at least in my opinion.

p.s. I wouldn't downvote something just due to priorities, not saying you did but just a side note. 😉

@bondsbw
Copy link

bondsbw commented Aug 11, 2017

I feel like these partial infix expressions could be a more general feature.

iqs.Where(x => x < 100)

could be simplified to

iqs.Where(< 100)

@iam3yal
Copy link
Contributor

iam3yal commented Aug 11, 2017

@bondsbw Well, if you think that this feature should be generalized then I think that it warrants a new post.

@bondsbw
Copy link

bondsbw commented Aug 12, 2017

I'll start a new post if there is interest.

@qaqz111
Copy link

qaqz111 commented Aug 25, 2017

The pattern match already can handle this for now:

            var a = 16;
            switch (a)
            {
                case int _ when a < 100:
                    Console.WriteLine(a);
                    break;
                case int _ when a < 200:
                    Console.WriteLine(a + a);
                    break;
            }

With when you can do a more complex play:

            var a = 16;
            switch (a)
            {
                case int _ when a < 100:
                    Console.WriteLine(a);
                    break;
                case int _ when IsOK(a):
                    Console.WriteLine(a + a);
                    break;
            }

        private bool IsOK(int a) => true; //<- replace with your own logic

The weak point is, you can not pass a long to the switch, and the when clause is indeed a bit long.

@gafter gafter self-assigned this Apr 19, 2018
@srburton
Copy link

Very good proposal

@gafter
Copy link
Member Author

gafter commented Jul 25, 2018

I'm championing this as the idea of having comparison-based pattern-matching forms.

@HaloFour
Copy link
Contributor

@gafter

Neat. Ideas around syntax?

if (person is Student { Gpa: > 3.0 }) { ... }

@gafter
Copy link
Member Author

gafter commented Jul 25, 2018

@HaloFour Yes, that syntax is fine. Also case > 10. Not sure how it should work when the input is of type object, for example.

@bondsbw
Copy link

bondsbw commented Jul 25, 2018

I'm curious if that syntax can be generalized into partial application.

@gafter
Copy link
Member Author

gafter commented Jul 26, 2018

@bondsbw Can you give an example? Remember these are patterns, not expressions.

For ranges, I expect the syntax would be something like in 1 to 10, for example

case in 1 to 10:
if (x is in 1 to 10)

etc.

@bondsbw
Copy link

bondsbw commented Jul 26, 2018

@gafter This would be outside of pattern matching, outside of the scope of the current proposal but related.

The idea would be to open up expressions like I mentioned above:

iqs.Where(< 100)

Say we have a binary operator x op y with the function signature Tx * Ty -> Tresult. Then x op would be a partial application with function signature Ty -> Tresult, and op y would be a partial application with function signature Tx -> Tresult.

To tie it all together, partial applications could be substituted for method groups with a compatible signature. < 100 has the signature int -> bool and is compatible with the predicate parameter of Where.

@bondsbw
Copy link

bondsbw commented Jul 26, 2018

A different idea... allow patterns to be substituted for any method group with signature T -> bool.

Then we could have

iqs.Where(is < 100)

but also the much broader range of patterns:

iqs.Where(is in 120 to 140)

people.Where(is Student { Gpa: > 3.0 })

@svick
Copy link
Contributor

svick commented Jul 26, 2018

@bondsbw How would your first idea work with operators that are both binary and unary, like +, - or *?

In other words, iqs.Select(- 100) is already valid syntax (which may or may not actually compile, depending on overload resolution), so I don't think you could change its meaning.

@bondsbw
Copy link

bondsbw commented Jul 26, 2018

@svick + and - are the only ones that could be ambiguous (I think... please correct me). In those cases it would have to prefer to interpret it as the unary operator, for the sake of BC.

I think I prefer the pattern approach, though it is limited to situations where the return value is bool.

@theunrepentantgeek
Copy link

Any operator with both a binary and unary form could trigger this ambiguity - which includes *, & and | as well.

Building on the pattern matching syntax we already have, the when syntax opens up every possible predicate in an extremely readable manner.

@Mattias-Viklund
Copy link

Mattias-Viklund commented Dec 5, 2018

Id like to propose an extension to this.

Where you can do something like this:

int a = 3;
int b = 2;
int c = 1;

switch (a) {
    case ((& c) == c):
        Console.WriteLine("(a & c) == c");
        break;
    case (>> 1 == c):
        Console.WriteLine("(a >> 1) == c");
        break;

    case (-b == c):
        Console.WriteLine("(a - b) == c");
        break;

}

Might be silly though.
(Sorry, not sure on how to format code in a comment)

@Unknown6656
Copy link
Contributor

@Mattias-Viklund your last switch case is a bit flawed, as it would be confusing whether you meant a-b == c or -b == c (both are currently valid expressions).

I would rather see a Haskell-like currying and partial application of operators, however, this would require currying in general to be introduced to C#. This would allow expressions such as:

Func<int, int> = (>>2);

...which could be applied to your example above in a more general manner.

@Mattias-Viklund
Copy link

Oh yeah of course, dumb oversight on my part. but yeah I would want something to add to switches in order to make them more flexible.

@zspitz
Copy link

zspitz commented Dec 18, 2018

Would this also work with strings, as in VB.NET?

var s = GetArbitraryString();
switch (s)
    case >= "n":
        Console.WriteLine("Past the midpoint");
        break;
    case "m" to "n":
        Console.WriteLine("Starts with 'm');
        break;
}

@Unknown6656
Copy link
Contributor

Unknown6656 commented Dec 19, 2018

@zspitz : Strings would have to define comparison operators like they do in VB.NET.
So, as long as they are not defined, your code wouldn't work.

However, you could currently "solve" your code sample by replacing strings with chars:

string s = ........;

switch (s[0])
{
    case >= 'n':
        Console.WriteLine("Past the midpoint");
        break;
    case 'm' to 'n':
        Console.WriteLine("Starts with 'm'");
        break;
}

@Thaina
Copy link

Thaina commented Jul 25, 2019

I would like to vote against the to syntax. It still very ambiguous

And I would favor and and or syntax in pattern instead

So 1 to 10 would just become >= 1 and <= 10 or > 0 and < 11

@gafter gafter added this to the 9.0 candidate milestone Aug 26, 2019
@gafter
Copy link
Member Author

gafter commented Aug 28, 2019

The LDM confirmed this as part of a set of pattern-matching features for consideration in C# 9.0. This depends for its utility on the and pattern combinator, so we would not do it before that.

@jcouv jcouv changed the title Proposal: allow comparison operators in switch case labels C# Proposal: allow comparison operators in switch case labels C# (VS 16.8, .NET 5) Sep 1, 2020
@MadsTorgersen MadsTorgersen modified the milestones: 9.0 candidate, 9.0 Sep 9, 2020
@333fred 333fred added the Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification label Oct 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Design Review Feature Request Implemented Needs ECMA Spec This feature has been implemented in C#, but still needs to be merged into the ECMA specification Proposal champion
Projects
None yet
Development

No branches or pull requests