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# #812

Open
gafter opened this issue Aug 10, 2017 · 26 comments
Open

Proposal: allow comparison operators in switch case labels C# #812

gafter opened this issue Aug 10, 2017 · 26 comments

Comments

@gafter
Copy link
Member

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

This comment has been minimized.

Copy link

@DavidArno DavidArno commented Aug 11, 2017

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

@bondsbw

This comment has been minimized.

Copy link

@bondsbw bondsbw commented Aug 11, 2017

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

@DavidArno

This comment has been minimized.

Copy link

@DavidArno DavidArno commented Aug 11, 2017

@bondsbw,

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

@eyalsk

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

@bondsbw 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)
@eyalsk

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

@bondsbw bondsbw commented Aug 12, 2017

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

@qaqz111

This comment has been minimized.

Copy link

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

@srburton

This comment has been minimized.

Copy link

@srburton srburton commented Jul 25, 2018

Very good proposal

@gafter

This comment has been minimized.

Copy link
Member Author

@gafter gafter commented Jul 25, 2018

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

@HaloFour

This comment has been minimized.

Copy link
Contributor

@HaloFour HaloFour commented Jul 25, 2018

@gafter

Neat. Ideas around syntax?

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

This comment has been minimized.

Copy link
Member Author

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

This comment has been minimized.

Copy link

@bondsbw bondsbw commented Jul 25, 2018

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

@gafter

This comment has been minimized.

Copy link
Member Author

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link

@theunrepentantgeek theunrepentantgeek commented Jul 26, 2018

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

This comment has been minimized.

Copy link

@Mattias-Viklund 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

This comment has been minimized.

Copy link
Contributor

@Unknown6656 Unknown6656 commented Dec 6, 2018

@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

This comment has been minimized.

Copy link

@Mattias-Viklund Mattias-Viklund commented Dec 6, 2018

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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Contributor

@Unknown6656 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;
}
@gafter gafter added this to TRIAGE NEEDED in Language Version Planning Mar 6, 2019
@gafter gafter mentioned this issue Jul 25, 2019
0 of 5 tasks complete
@Thaina

This comment has been minimized.

Copy link

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

@MadsTorgersen MadsTorgersen moved this from TRIAGE NEEDED to 9.0 Candidate in Language Version Planning Aug 26, 2019
@gafter gafter added this to the 9.0 candidate milestone Aug 26, 2019
@gafter

This comment has been minimized.

Copy link
Member Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.