Skip to content

Latest commit

 

History

History
90 lines (62 loc) · 5.22 KB

LDM-2021-05-26.md

File metadata and controls

90 lines (62 loc) · 5.22 KB

C# Language Design Meeting for May 26th, 2021

Agenda

  1. Open questions in list patterns

Quote of the Day

  • "If somebody lays down on the track I'm perfectly willing to be taken hostage"

Discussion

Open questions in list patterns

https://github.com/dotnet/csharplang/blob/main/proposals/list-patterns.md

Today we looked at a number of open questions in list patterns, currently being implemented.

Patterns allowed after a slice operator

We first revisited the question of what to allow after a .. pattern. Previously we stated that any possible pattern should be allowable in this position; however, there is some concern that this could be confusing to code readers. We forsee the vast majority of patterns in this location being simply capturing the slice into a pattern, and there is a dissimilarity here with other containing pattern contexts. For positional, property, list, and length patterns, nested patterns are all visually inside delimiters ((), {}, and [], respectively). Here, the pattern would be on the slice element, but not visually inside. There is also some concern that perhaps we should take the position that using a nested pattern here is just generally not good form: we think that such syntax will become quickly unreadable, going against the general goal of the feature. There's also potential for being visually hard to parse: something like ..var x and not null parses very differently in a pattern (as ..(var x and not null)) than it does in regular syntax, for something like ..1+2 (which parses as (..1)+2).

Looking at these concerns gives us 3 general options for how to move forward:

  1. Simplify the syntax, and only allow ..identifier. Users can omit the type entirely. General patterns are not allowed.
  2. Only allow ..var pattern. This has symmetry with other declaration patterns.
  3. Allow all patterns.

Option 1 is initially somewhat attractive because it will simplify the 99% case here. However, we have some concerns: it's not regular with other declarations, and some users do not like implicitly-typed variables. It also makes it harder to change our minds here in the future, if we ever wanted to add an all or any pattern. Meanwhile, version 2 suggests a pattern generality that would not exist, and (to the user that runs into this) for seemingly arbitrary reasons.

Given our concerns with the first two approaches, we think that, despite earlier concerns, approach 3 is the way to go. We can take a stronger stance in the IDE and our documentation with fixers and best practices to help ensure that nested patterns don't get too crazy here.

Conclusion

Original design upheld, any pattern is allowed after a slice.

Multi-dimensional array support

Currently, the list pattern specification has an open question as to whether MD-arrays should be supported. One of our goals for this feature was to bring symmetry between object creation and deconstruction/pattern forms, and array/collection initializers work with MD-arrays today, suggesting they should be supported in patterns. However, our current rules depend on types being indexable/countable/sliceable, which MD-arrays are not today. They're also generally not a huge area of investment for either the language or the runtime, so it feels odd to make sure they work here but not also with the rest of the indexing/counting/slicing features.

Conclusion

Not supported. If we want to make a general MD-array focused release, we would want to revisit all the areas they're currently lacking, not just list patterns.

Binding rules for Slice

This is a double-sided question on how we bind Slice methods: should we allow default parameters, and should we allow extension methods for Slice? In general, we think we should follow the same rules as the existing support around slicing, which is no on both questions. We can look at a general feature to loosen the restrictions as a separate proposal.

Conclusion

No default parameters or extension methods.

Tests emitted for { .. }

The main question here is whether { .. } should emit a check that Length or Count is greater than or equal to 0, or if it should simply be a no-op. This must be specified in the language, as not doing so would result in default(ImmutableArray<int>) is { .. } having undefined behavior (it will throw or not, depending on whether Length is evaluated). We think that .. without a trailing pattern and without any other element patterns is similar in concept to a _ pattern: default(ImmutableArray<int>) is { Length: _ } does not actually evaluate Length, and thus won't throw. The .. is the same: other things cause Length to be evaluated, such as the precense of other element patterns or a length pattern. A slice pattern can change the nature of that check (moving it from an == to a >=), but it doesn't add the check on its own. Similarly, .._ will not call slice, just like other discard operations. This does mean that, for list implementations that do not follow framework design guidelines and return negative lengths or counts, { .. } will not fail when they do so, but we think that is fine.

Conclusion

{ .. } will not emit a Length check, and { .._ } will not call Slice.