csharplang/meetings/2021/LDM-2021-04-12.md
2021-07-27 12:34:38 -07:00

5.7 KiB

C# Language Design Meeting for April 12th, 2021

Agenda

  1. List patterns
  2. Lambda improvements

Quote(s) of the Day

  • "If the runtime team jumped off a bridge, would you [redacted]?" "If it would make my code faster"
  • "Quote of the day may need to involve corn fields"

Discussion

List patterns

https://github.com/dotnet/csharplang/issues/3435

Today we looked over the changes around supporting IEnumerable types in list patterns, and how slice patterns will affect them. While we overall like the lowering that has been proposed, we would like to continue to look at possible optimizations around using framework apis such as TryGetNonEnumeratedCount. This will allow the framework to do all the grungy bookkeeping to make this quick for cases when the IEnumerable is actually a countable type, or one of a certain type of LINQ expression that is countable under the hood (such as a Select over an array). The runtime is also planning on adding a Take method that does similar bookkeeping with index and range, falling back to buffering if it has to.

We also considered to what extent we should allow sub-patterns under a slice pattern. The obvious use case is a declaration pattern for the slice, but as worded today the syntax will allow any arbitrary sub-pattern on the slice. After discussion, while we think that the main use case is just declaration patterns, we see no reason to disallow other nested sub-patterns. It should compose well, and while 99% of users will never need it, it could be helpful for that 1%.

The other question with slice patterns is whether to allow nested sub-patterns for IEnumerable as well as for sliceable types. If we do allow this, it would mean treating IEnumerable specially here, while indexers and slicers cannot be used in the general case. Given this, we think it will be a better experience if we only allow sub-patterns under a slice if the type is actually sliceable. Separately, we can look at considering extension methods as a part of general sliceability, which, if combined with renaming that Take method from the framework to Slice before it ships, would enable the feature in a much more general fashion.

Conclusions

  • Lowering is generally approved.
  • Slice sub-patterns will allow any pattern, so long as the input type to the list pattern is sliceable.
  • Making IEnumerable sliceable is orthogonal.

Lambda improvements

https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/lambda-improvements.md

After some implementation work, this proposal is back to look at some more changes. One of the big ones is moving the return type to the left side of the parameter list. This makes the syntax analogous to our other signature declarations in C#, looking like an unnamed version of a local function. This fits in well with the types of changes we've been making to C# in recent versions, such as tuples/record structs, anonymous classes/record classes, and declaration forms/pattern forms. While we do have Func and delegate* as types that put the return last, we are at least consistent within type forms and within signature declaration forms. At this point, the only parity we are missing between lambdas and local functions is the ability to declare generic type parameters, but those won't be useful on lambda expressions unless we add higher-kinded types to the language.

We also looked at requirements around return type specification: do we want to require that, if a return type is specified, parameter types must be specified too? On the one hand, we require that, if any parameter types are specified, the others must be specified as well. On the other, we can't make it required to specify the return type when a parameter type is specified because C# hasn't had such an ability until now, so requiring this would lack symmetry. It's also very possible that the return type of a lambda could be uninferrable, while the parameter types are, and it would be unfortunate if you had to specify everything in order to just get the return type. Given this, we will allow the return type to be specified without parameter types.

We considered the case of async async (async async) => async. We could make it required to escape async used as a type name here: if the user wants to specify the return type of the lambda is the type named async and the lambda is not an async lambda, they'll have to escape it anyway to disambiguate between making the lambda async. Given this, we support making it required to escape the type name in the return type.

The changes around attributes in the proposal raised no concerns. These changes were requiring that, if an attribute is used, parentheses must be used for the parameter list, and the rules around how the compiler will disambiguate conditional array accesses and dictionary initializers.

Finally, we noted the possibility for a breaking change if single-method method groups are given a natural type with the following code:

using System;

var c = new C();
D.M(c.MyMethod);

// Outputs "Extension" today, would print Instance if c.MyMethod had a natural type.

class C
{
    public void MyMethod() { Console.WriteLine("Instance"); }
}

static class CExt
{
    public static void MyMethod(this C c, string s) { Console.WriteLine("Extension"); }
}

class D
{ 
    public static void M(Delegate d) { d.DynamicInvoke(); }
    public static void M(Action<string> a) { a(""); }
}

We'll need to investigate to see if we can avoid this change, or if we're ok with the potential break.

Conclusions

  • Return type before the parameter list is approved, does not require parameter types to be specified.
  • async as a return type name should require escapes.
  • Attribute changes are approved.