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

Make case optional in switch statements? #2618

Closed
mnordine opened this issue Nov 9, 2022 · 6 comments
Closed

Make case optional in switch statements? #2618

mnordine opened this issue Nov 9, 2022 · 6 comments
Labels
patterns Issues related to pattern matching.

Comments

@mnordine
Copy link
Contributor

mnordine commented Nov 9, 2022

It's kinda sad that we need the case keyword for every case:

final a = switch (s) {
  case 'a' => 'A';
  case 'b' => 'B';
  case 'c' => 'C';
};

Would love to be able to write:

final a = switch (s) {
  'a' => 'A';
  'b' => 'B';
  'c' => 'C';
};

Or:

final a = switch (s) {
  'a': 'A';
  'b': 'B';
  'c': 'C';
};

(the last example is orthogonal and being discussed here)

After pattern matching gets out, I assume a good bit of code will be updated to be expressed in them, and it'd be nice to have them syntactically optimal. Apologies if there's an obvious reason this can't be done, but thought I'd raise the issue, just in case.

@mnordine mnordine added the feature Proposed language feature that solves one or more problems label Nov 9, 2022
@munificent
Copy link
Member

I just saw the issue you filed but take a look at my suggestion here: #2126 (comment).

The short answer is, yes, think we could and should eliminate case for switch expressions but not for switch statements.

@munificent munificent added patterns Issues related to pattern matching. and removed feature Proposed language feature that solves one or more problems labels Nov 11, 2022
@lrhn
Copy link
Member

lrhn commented Nov 25, 2022

I personally disagree. I don't think we should not remove case from expression patterns.

It's slightly more verbose, but it also means that case pattern is the same syntax for a refutable pattern everywhere.

I disagree strongly with making expression-switch and element-switch have different syntaxes. If we omit case, we should omit it for both.

I'd prefer:

// statement
switch (e) {
  case p when e:
  case p when e: 
     statements
  default: 
     statements
}
// expression:
var _ = switch (e) {
  case p when e =>
  case p when e => 
     expression
  default =>
     expression
}
// element:
var _ = [
  switch (e) {
    case p when e => 
    case p when e => 
      element
    default =>
      element
   }
];
// if statement
if (e case p when e)  
  statement
else 
  statement
// if element
var _ = [if (e case p when e) element else element];

No separator characters needed between cases, the case body is terminated by the following case or }.
Always has case in front of a refutable pattern, and the refutable pattern follows the matchee expression in the program, so we don't start thinking there is type inference going from the pattern to the expression. (That's what disqualifies if (var p when e = e) to me. That, and not being able to read it.)

If we do remove the case from expressions, then we need a separator. Then I think => and , are good choices.

I'll want the same thing for switch-element cases. They are closer to expressions than to statements, even if they can contain for/if elements, those are still behaving more like optional expressions than statements. (But that's a discussion we can take when we introduce element switches.)

@munificent
Copy link
Member

munificent commented Nov 29, 2022

I don't see a super compelling reason to make switch expressions consistent with switch statements. That isn't how the corresponding expression forms for conditionals work. If you line them up:

StatementElementExpression
if (e)
  statement
else
  statement
if (e)                
  element             
else                  
  element             
 e ? expression : expression
if (e case p when e)
  statement 
else 
  statement 
if (e case p when e)
  element
else
  element

No expression form.

switch (e) {       
  case p when e:   
  case p when e:   
     statements    
  default:         
     statements    
}                  
switch (e) {         
  case p when e:     
  case p when e:     
     element         
  default:           
     element         
}                    
switch (e) {
  p when e => expression,
  p when e => expression,
  _ => expression
}

As you can see, the element form always strictly follows the statement form. Then the expression form is basically its own thing.

I like consistency in general, of course. But I think the context for statements and expressions are different enough that they push those syntaxes in different directions. For expressions, brevity is more important so that you don't lose sight of the larger surrounding structure. And we don't need case keywords there because each case body is only a single statement. With switch statements, we need some explicit delimiter like case because each body can be an open-ended series of statements.

There is an interesting question about what the body of switch elements should be. Since it follows switch statements, that suggests allowing a series of elements in each case:

var items = [
  switch (c) {
    case 'three':
      1,
      2,
      3,
    case 'many':
      4,
      ...[5, 6, 7],
    case 'nested':
      if (d)
        8,
      9,
      for (var i = 10; i < 20; i++)
        i
  }
];

I'm not sure if that's a good idea or not, though.

@lrhn
Copy link
Member

lrhn commented Nov 29, 2022

I love the idea of "multi-elements" in elements in general (the ...[1, 2, 3] workaround is ugly).
I'd be sad if switches were the only way to get them.

I don't want to do [1, 2, if (test) switch (true) { case true: 3, 4, 5 }] to avoid the intermediate list allocation. (And I don't trust our compilers to omit it, not until they publicly promise to do so.)

That said, it does look very compelling when written out this way. I think I might be able to live with it :)

(Are there any other control flow constructs that we want to introduce in expressions? #2505 is just about break/continue/return, which are trivial. We have switch now. We have ?/: for condition. Only thing we lack is try/catch/finally and loops, and loops are not going to be simple for expressions - might even suggest that if we ever do expression loops, they too will have completely new syntax. So, maybe not.)

I think we should allow (e1 case p) ? e2 : e3 in an expression too, #2664, for great orthogonality.

@munificent
Copy link
Member

munificent commented Nov 29, 2022

I love the idea of "multi-elements" in elements in general (the ...[1, 2, 3] workaround is ugly).

I don't love ...[1, 2, 3] either, but I do like that it's more or less consistent with using { stmt1; stmt2; stmt3; } as an explicit delimited block when you want to have multiple statements in a context where a single statement is expected. And I like that there isn't a special "element block" form, it just falls out naturally from the spread syntax.

I don't want to do [1, 2, if (test) switch (true) { case true: 3, 4, 5 }] to avoid the intermediate list allocation. (And I don't trust our compilers to omit it, not until they publicly promise to do so.)

I would have a lot more faith in our compilers optimizing ...[3, 4, 5] than a a switch. :)

That said, it does look very compelling when written out this way. I think I might be able to live with it :)

\o/

(Are there any other control flow constructs that we want to introduce in expressions? #2505 is just about break/continue/return, which are trivial.

Yeah, I would definitely like break, continue, and return. I think switch expressions will make those actually more pressing because if you have code like:

foo(int n) {
  String s;
  switch (n) {
    case 0: s = 'blah';
    case 1: s = 'whatever';
    default: return 'invalid';
  }
  return s.toUpperCase();
}

It would be nice to convert that to a switch expression, but the case that returns makes that not work. But if return was an expression, you could do:

foo(int n) {
  return switch (n) {
    0 => 'blah',
    1 => 'whatever',
    _ => return 'invalid';
  }.toUpperCase();
}

Only thing we lack is try/catch/finally

I would like to have try as an expression because right now you have to dance around uninitialized variables if you want to calculate a value in the try block but not use it until after. You end up writing code like:

int n; // Gross. :(
try {
  n = int.tryParse('1234');
} on FormatException {
  n = 0;
}

It would be cool if you could write:

var n = try {
  int.tryParse('1234');
} on FormatException {
  0;
}

But that gets into weird territory around making blocks expressions and so far I've been afraid to go there.

and loops, and loops are not going to be simple for expressions - might even suggest that if we ever do expression loops, they too will have completely new syntax. So, maybe not.)

I can't recall which one, but I know there's some language out there that treats loops as expressions and allows a value after break which becomes the value that the loop evaluates to. Something like that could be interesting, but I'm not sure if it's worth it.

@munificent
Copy link
Member

Closing this. I think we've settled on eliminating case for switch expressions, but keeping it for switch elements and statements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
patterns Issues related to pattern matching.
Projects
None yet
Development

No branches or pull requests

3 participants