csharplang/meetings/2018/LDM-2018-04-30.md
2018-05-18 16:04:30 -07:00

5 KiB

C# Language Design Notes for Apr 30, 2018

Warning: These are raw notes, and still need to be cleaned up. Read at your own peril!

Switch expressions

Current syntax:

e switch
{
    1 => "one",
    2 => "two",
    var x when x > 2 => "too many",
    _ => "too few"
}

No default - instead use _.

If the compiler thinks there are cases you don't handle, it'll warn. If you actually don't handle a case we throw an exception (NRE for prototype, something else in the long run).

Also discussed:

  • use match instead of switch
  • keyword first, followed by parens, like switch statement
  • or without parens
  • optional implicit default at the end?

The current thing is nice for error recovery.

The lack of statements inside may be a frustration, but that's orthogonal. Let's leave it for now.

Exploration

Is there a way to instead generalize the syntax from the conditional (ternary) operator? After all, semantically speaking this could be viewed as a general form: Conditional operators and switch expressions on bool are semantically equivalent.

e
    ? true => e1 
    : false => e2

The fact that you know the number of colons today means you can have fewer parentheses than you would get away with here.

e ?
    true => e1 ? x : y :
    false => e2

It's not in fact obvious whether there should be many ?s and one :, or one ? and many :s:

// Interpret ? as following the tested expression, and : as a separator of test/result pairs
e
    ? 1 => "one"
    : 2 => "two"
    : var x when x > 2 => "too many"
    : _ => "too few"

// Interpret ? as introducing test/result pairs, and : as introducing the fallback result
e
    ? 1 => "one"
    ? 2 => "two"
    ? var x when x > 2 => "too many"
    : "too few"

The => glyph is probably not right in a ?: style syntax. It would have to be something else that more clearly signals pattern/result pairs.

e
    ? 1 -> "one"
    ? 2 -> "two"
    ? var x when x > 2 -> "too many"
    : "too few"
    ```
    
## Considering our options

``` c#
// Compromise - terser
e ? { 1: "one", 2: "two", var x when x > 2: "two many", "too few" }

// Formatted
e ? 
{ 
    1: "one", 
    2: "two", 
    var x when x > 2: "two many", 
    "too few" 
}

e switch 
{ 
    1: "one", 
    2: "two", 
    var x when x > 2: "two many", 
    "too few" 
}

// Some of these in context of a var
var x = e
    ? 1 -> "one"
    : 2 -> "two"
    : var x when x > 2 -> "too many"
    : _ -> "too few";

var x = e ? 
{ 
    1: "one", 
    2: "two", 
    var x when x > 2: "two many", 
    "too few"
};

var x = e switch 
{ 
    1: "one", 
    2: "two", 
    var x when x > 2: "two many", 
    "too few" 
};

// Some of these as one-liners in a method call

M(x switch { null => 0, _ => x.Length });   // 1
M(x switch { null: 0, x.Length });          // 2

M(x ? null -> 0 : _ -> x.Length);           // 3
M(x ? { null: 0, x.Length });               // 4

M(x ? null -> 0 : x.Length);                // 5
M(x ? { null -> 0, x.Length });             // 6

Argument against 1:

strings.Select(x => x switch { null => 0, _ => x.Length });   // Lots of => with different meaning

Argument against ->: Has meaning in unsafe code

Argument against : as used in 4: Clashes with other uses of :.

Where input is an expression rather than a variable:

M(e switch { null => 0, var x => x.Length });   // 1 - 0
M(e switch { null: 0, var x: x.Length });       // 2 - 13 - 6

M(e ? null -> 0 : var x -> x.Length);           // 3 - 1
M(e ? { null: 0, var x: x.Length });            // 4 - 6  - 3

M(e ? { null -> 0, var x -> x.Length });        // 6 - 0

We might want to allow the last thing to be a default value without pattern, but not in the prototype.

Conclusion

The prototype will have version 2. We're saving for later whether the last clause should be able to leave off a pattern.

Property pattern

if (e is { Name: "Mads", Employer: { ID: string id } }) { WriteLine(id); }          // 1 - Current
if (e is { Name = "Mads", Employer = { ID = string id } }) { WriteLine(id); }       // 2
if (e is { Name == "Mads", Employer == { ID == string id } }) { WriteLine(id); }    // 3
if (e is { Name is "Mads", Employer is { ID is string id } }) { WriteLine(id); }    // 4

1 is what we have implemented, but it clashes a little with what we just decided for switch expressions. 2 mirrors object initializers the most 3 implies equality, but clashes in meaning with == elsewhere 4 emphasizes is as a means for applying patterns

var result = person switch 
{ 
    { Name: "Mads", Employer: { ID: string id } }: id,
    (_, id: var pid){ Name: "Matt" }: pid,
    _: null
};

There's a clash but maybe it doesn't feel too bad. The second pattern with three different meanings of : is certainly not common.

var result = person switch 
{ 
    { Name is "Mads", Employer is { ID is string id } }: id,
    (_, var pid){ Name is "Matt" }: pid,
    _: null
};

Conclusion

Decision: stay with : for prototype, remains open question though!