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: Permit expression statements within the new switch expression and allow it to be an expression statement itself #1558

Closed
DavidArno opened this issue May 23, 2018 · 13 comments

Comments

@DavidArno
Copy link

There are two parts to this proposal, that both hinge on my understanding of expression statements. There is a high risk that I've got it wrong...

Within the proposal, #1548, the following syntax form is used as an example in a number of cases:

void DoSomething(Shape s) => s switch 
{
    Circle c when c.Radius > 10 => DoSomethingWithCircle(c),
    Rectangle r when r.Width > 10 => DoSomethingWithRectangle(r),
    _ => DefaultAction()
};

Under the current plans for the switch statement, this will not compile if DoSomethingWithCircle, DoSomethingWithRectangle and DefaultAction are void and/or if DoSomething has a void "return type". Many of the errors that various forms of this code give relate to a switch expression not being in the list of accepted expression statements.

As I understand it, the set of statements permitted in an expression-bodied method is confined to those defined as an expression_statement. So there is precedence in the language for supporting such constructs. Therefore, I propose that the cases in a switch expression should themselves should support expression_statement statements. I think it already does do this, but I wanted to formalise that here.

Next, the switch expression itself should be added to the expression_statement set, which would allow it to be used as a statement, and would allow it to be used as the expression in a void expression-bodied method.

Doing this has two advantages:

  • It permits the switch expression to be used as a compact alternative to the switch statement in those specific situations where we simply want to invoke one invocation, assignment etc action per case, eg,
caseSwitch switch
{
    1 => Console.WriteLine("Case 1"),
    2 => Console.WriteLine("Case 2"),
    _ => Console.WriteLine("Default case")
};
  • It permits those simple switches to form the body of methods using the expression-bodied method syntax.
@svick
Copy link
Contributor

svick commented May 23, 2018

Therefore, I propose that the cases in a switch expression should themselves should support expression_statement statements. I think it already does do this, but I wanted to formalise that here.

I don't understand what this means. Each "switch expression arm" (the term used by the compiler prototype for switch expression cases) has an expression, not a statement after the arrow. So I don't understand how are expression_statements relevant here.

@DavidArno
Copy link
Author

@svick,

I am quite likely using the wrong terms. Just as an expression-bodied method can either have an expression (if it returns a value), or an expression_statement (if it's void), I'm proposing that the "arms" of a switch expression likewise can have an expression or expression_statement.

@svick
Copy link
Contributor

svick commented May 23, 2018

@DavidArno

I am quite likely using the wrong terms. Just as an expression-bodied method can either have an expression (if it returns a value), or an expression_statement (if it's void), I'm proposing that the "arms" of a switch expression likewise can have an expression or expression_statement.

I think I understand what you mean now. The term you're looking for is statement_expression, not expression_statement. (I can certainly see why someone would confuse the two.) From the spec:

When a method has a void result and an expression body, the expression E must be a statement_expression, and the body is exactly equivalent to a block body of the form { E; }.

And you're proposing the same limitation for the expressions in the arms of a switch expression, if the switch expression is treated as a statement expression.

@DavidArno
Copy link
Author

Thanks for the clarification re a statement expression and expression statement. I'd missed the fact that they were different things.

I think it might already support those statements in the arms of the switch expression. For example, the following code only complains on the void discard (or if I try to use the switch expression as a statement):

class Program
{
    static void M(Shape s)
    {
        _  = s switch 
        {
            Circle c when c.Radius > 10 => DoSomethingWithCircle(c),
            Rectangle r when r.Width > 10 => DoSomethingWithRectangle(r),
            _ => DefaultAction()
        };
    }
   
    private static void DefaultAction() {}
    private static void DoSomethingWithCircle(Circle circle) {}
    private static void DoSomethingWithRectangle(Rectangle circle) {}

}
class Shape{}
class Circle : Shape
{
    public int Radius;
}

class Rectangle : Shape
{
    public int Width;
}

So I think the only thing I'm actually asking for here is for a switch expression to be treated as a statement expression (or expression statement; not sure which 😄)

@HaloFour
Copy link
Contributor

I don't think that this would work terribly well without changing how expressions work in C# on a larger scale. As with the ternary conditional operator the C# compiler is going to expect that all arms collapse to a single type which will become the type of that expression. So if any of those functions or expressions invoked in an arm happen to return a value that would result in a compiler error:

s switch  {
    Circle c when c.Radius > 10 => circles.Add(c),  // Add happens to return bool
    _ => Console.WriteLine("nope")
};

This is different from where you can use statement expressions today because the compiler knows that the expression would result in void:

Action a = () => Console.WriteLine("foo");
void lfn() => Console.WriteLine("bar");

But the compiler won't know that here, not unless it considers how the result might be assigned. Attempting to have the compiler resolve void if the result of the switch expression goes unassigned could interfere with the sequence expression or block expression proposals.

@DavidArno
Copy link
Author

@HaloFour,

... As with the ternary conditional operator the C# compiler is going to expect that all arms collapse to a single type which will become the type of that expression. So if any of those functions or expressions invoked in an arm happen to return a value that would result in a compiler error...

This already happens with the recursive patterns branch. For example, in the code above, change DefaultAction to be a bool method:

private static bool DefaultAction() => true;

and the only error from the compiler is error CS8406: No best type was found for the switch expression. So it looks like the switch expression already handles void statement expressions in its arms and will complain if they re not all void.

So I don't think your concern is valid here.

@HaloFour
Copy link
Contributor

HaloFour commented May 23, 2018

@DavidArno

So I don't think your concern is valid here.

That the compiler would so quickly fail is my concern. It makes a large number of methods that happen to return a value but where that value is safely ignored unsuitable to be used here. This isn't an issue anywhere else that you can use a statement expression because the compiler knows to drop the return value on the floor:

public void M() {
    bool lfn() => true;

    Action a = () => lfn(); // no problem

    x switch {
        V1 _ => lfn(), // problem
        V2 _ => Console.WriteLine("foo")
    };
}

I'm not saying that this is a problem from a technical point of view, but that it's a problem from a user experience and expectation point of view.

@DavidArno
Copy link
Author

I'm not saying that this is a problem from a technical point of view, but that it's a problem from a user experience and expectation point of view.

I disagree. The compiler error is perfectly clear: you can't mix types in the arms of the switch expression. You'd get exactly the same problem if you mixed eg a bool and int expressions.

@HaloFour
Copy link
Contributor

@DavidArno

You'd get exactly the same problem if you mixed eg a bool and int expressions.

If you expected to assign that anywhere I would agree it doesn't make sense.

I guess what I'm saying here is that if you want to treat the switch expression as a statement that I would rather it act like a statement and not care what the return values of any of the expressions are. I guess you're used to functional languages being finicky here as everything in an expression.

@gafter
Copy link
Member

gafter commented May 23, 2018

I think that what would be needed to make this work is that void should be treated as the most specific type if it is one of the candidates.

@gafter
Copy link
Member

gafter commented May 23, 2018

Added this as an open issue for pattern-matching.

@DavidArno
Copy link
Author

@gafter, that sounds like a neat compromise between @HaloFour's position and mine. Nice one.

Of course it might be @HaloFour's actual position reworded, in which case I'm now clearer on that position and happy for that to be the way things are. 😃

@DavidArno
Copy link
Author

Closing this issue as @gafter has captured the request in #1054.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants