csharplang/meetings/2021/LDM-2021-05-26.md
2021-05-27 21:38:37 -07:00

91 lines
5.2 KiB
Markdown

# C# Language Design Meeting for May 26th, 2021
## Agenda
1. [Open questions in list patterns](#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`.