dotnet / csharplang

The official repo for the design of the C# programming language
11.4k stars 1.02k forks source link

Idea for `switch` expression syntax #663

Closed lachbaer closed 6 years ago

lachbaer commented 7 years ago

I recently had an idea for a less verbose version of a switch ('match') expression. It resembles the classical ternary operator with ? :

var area = case(switch) ?
         : Line l => 0
         : Rectangle r => r.Width * r.Height
         : Circle c => Math.PI * c.Radius * c.Radius
         : throw new ApplicationException();

There are three major differences.

  1. The case operator before the ? makes this first expression a case expression instead of a boolean.
  2. Between ? and the first : is nothing but whitespace
  3. Multiple case-sections are seperated by : without a preceeding comma or so

Because the expressions return a value (or throw) I chose the => token to seperate the pattern from the following expression. But this, like all the rest of course, is up to discussion.

kasajian commented 7 years ago

It's not really "ternary" anymore if it's not composed of three parts.

So let's say you have code like this today:

int x;
switch (v)
{
    case 0:
        x = 10;
        break;
    case -1:
        x = 37;
        break;
    case 5:
        x = 99;
        break;
    default:
        x = default(int);
        break;
}

The above code is equivalent to the following:

int x =
    v == 0 ? 10 :
    v == -1 ? 37 :
    v == 5 ? 99 :
    default(int);

The question is, is the following syntax, using a proposed modified form of switch/case desirable and have any benefit:

int x = switch (v)
    case 0: 10
    case -1: 37
    case 5: 99

Note the implied default case.

gafter commented 7 years ago

The question is, is the following syntax, using a proposed modified form of switch/case desirable and have any benefit

That syntax form has the "dangling else" problem in a pretty bad way when switch expressions are nested.

kasajian commented 7 years ago

Can't the "dangling else" problem be solved by convention, by saying it will take a greedy path or something like that, or is that because C# can't introduce ambiguous aspects in its grammar?

If you had code such as this:

int x = switch (v)
    case 0: 10
    case -1: switch (w) case 55: 5 case 66: 6 case 77: 7
    case 5: 99

You can assume that the case 5: 99 is part of the switch (w), unless you wrote it like so:

int x = switch (v)
    case 0: 10
    case -1: (switch (w) case 55: 5 case 66: 6 case 77: 7)
    case 5: 99

I'm guessing that you're saying this will break something??

gafter commented 7 years ago

@kasajian Yes, the something it will break is user's brains. We should avoid ambiguities in the language, as that is likely to result in broken user expectations.

kasajian commented 7 years ago

As far as user's brain is concerned, doesn't that currently happen now?

    int x = 0;

....

    if (x == 0) x = 10; if (x == 1) x = 11; else x = 20;

Where does the else go with? The first if or the second if ?

jnm2 commented 7 years ago

I think the canonical example is to leave out the x = 10;.

kasajian commented 7 years ago

I just Googled, and this language seems to be doing this: https://ceylon-lang.org/documentation/reference/expression/switch/

Looks pretty clear to me.

gafter commented 7 years ago

@kasajian Yes, that is an issue we inherited from C. That is one reason we discourage the controlled statement from being written without curly braces. But for the match expression, we don't inherit anything from C.

QuickC commented 7 years ago

@lachbaer I think the real issue is deciding if this short version is to be an expression, with no worries of a dangling clause, or a statement, witch needs a way to handle dangling.

I think if your going to make a modern short syntax then an expression is required and following the ternary idea has great merit. It removes the need to have assignment in each clause, and the token required to separate it. The "test" needs to allow for decomposition also. var ans = (obj1, obj2) ? obj1==test1 => ans1, obj1== && obj2 == test2 => ans2, = defaultAns;

var answer = (obj1, obj2) ? obj1==test1 => ans1, obj1== && obj2 == test2 => ans2, = defaultAns;

gafter commented 6 years ago

The current proposal is the following:

var area = shape switch {
    Line l => 0,
    Rectangle r => r.Width * r.Height,
    Circle c => Math.PI * c.Radius * c.Radius,
    _ => throw new IllegalArgumentException()
    };

The syntax is still open to possible change, and will be discussed in future LDM sessions.

Closing as discussion on this issue seems to have calmed down.

Thaina commented 6 years ago

@gafter Just wonder what if we make a block with only one case and default, would it be any difference in performance than if/else?

var area = shape switch {
    Rectangle r => r.Width * r.Height,
    default => 0
};

// vs

var area = 0;
if(shape is Rectangle r)
    area = r.Width * r.Height;

ps. I would like the real syntax to be default instead of _

gafter commented 6 years ago

@Thaina I would expect identical performance.

We do not use default for a few reasons: