Skip to content

Latest commit

 

History

History
73 lines (54 loc) · 5.81 KB

LDM-2021-11-01.md

File metadata and controls

73 lines (54 loc) · 5.81 KB

C# Language Design Meeting for November 1st, 2021

Agenda

  1. Order of evaluation for Index and Range
  2. Collection literals

Quote of the Day

  • "So 'The customer wants it therefore it's wrong' is your new motto?"

Discussion

Order of evaluation for Index and Range

dotnet/roslyn#57349

We looked at an inconsistency in the order of evaluation between Index and Range operations, when those are applied to a type that does not have native methods that accept Index or Range types. The Index portion of the spec is fairly straightforward and we were easily able to determine the compiler had a bug with the lowering it performed. However, the specification is unclear about the Range translation: it does not clearly communicate the order of evaluation for the receiver, Length, and expr expressions that are cached. Our current behavior is inconsistent with the Index behavior, and likely confusing to users. After a bit of discussion, we unanimously agreed to standardize the order of evaluation to be consistent with the written original order in the source, followed by compiler-needed components if necessary. For indexers, this is receiver, then expr, then Length if needed.

Conclusion

Codify the expected behavior as receiver then expr then Length.

Collection literals

#5354

We took a first look at a proposal for a "collection literal" syntax in C#, doing a general overview of the proposed syntax form and gathering general feedback on the proposal. We hope to approach this proposal as a "one syntax to rule them all" form, that can achieve correspondence with the list pattern syntax form, support new types that collection initializers can't today (such as ImmutableArray<T>), and serve as a zero or even negative cost abstraction around list creation that is as good or better than hand-written user code.

There's some unfortunate bad interaction with existing collection initializers, however. They'll still exist, and they will be advantageous in some cases. Since the user is explicitly calling new Type on them, they have an obvious natural type, while collection literals will need some form of target-type for cases where there is no obvious natural type or if the user is trying to select between ambiguous overloads. They also probably wouldn't support this new splat operator, which also makes them worse in that case; even if we extended support for it, it's very likely that the new form would lower to a more efficient form with the pre-length calculation that it able to support.

We're unsure about some of the various axes of flexibility in this space. Some of them that we talked about are:

  • Existing collection initializers require the type to implement IEnumerable to be considered collection-like. Do we want to keep this restriction for the new form? It would lead to an odd non-correspondence with list patterns, since they are pattern-based and not interface-based. The proposal actually started more restrictive, but loosened to the current form after considering things like HashSet<int> h = [1, 2, 3]; and deciding that was perfectly fine.
  • How specific do we want to make the lowering for various forms? Since it's a goal of the feature to be as optimal as possible here, if we prescribe too specific of lowering forms then we potentially make it difficult to optimize the implementation. We know from experience, though, that while we'll have a small window of opportunity just after ship to make changes to the way code is emitted, making changes years on will be dangerous and will likely have impacts on some user's code.
  • We think that a natural type for this syntax will have a lot of benefits, such as allowing it to be used for interfaces (IEnumerable<int> ie = [1, 2];) and it will be in line with the recent work we did around lambda expressions. However, unlike lambdas, we don't have a single list type in .NET that is unambiguously the natural type it should be. We'll need to dig into the pros and cons of the space and decide on how we want to approach this.

We also talked briefly about possible extensions of this space. One obvious one is dictionary literals. We think we can move ahead with collection literals for now, as long as we keep both dictionary literals and dictionary patterns, to be sure we're holding ourselves to the correspondence principle. Another area we should investigate is list comprehensions. We're not sure whether we want comprehensions, but we should at least look at the space and make sure we're comfortable that we'd never consider them or leave ourselves the space to be able to consider them in the future.

Finally, we want to look at other contemporary languages and see what they do for this space. For example, F# has dedicated syntax forms for each of it's main list/sequence types. Unlike C#, they have largely unified on a very few specific collection types. This allows them to have dedicated syntax for each one, but this approach isn't likely to work well in C# because we have a much wider variety of commonly-used collections, tuned for performance across a variety of situtations. Kotlin takes a different approach of having well-known methods to construct different collections, such as listOf or arrayOf. It's possible that, with params Span, we'd be able to achieve 90% of what we're trying to do without needing to build anything into the language itself. And then there are languages like Swift, Python, Scala, and others that actually have a dedicated literal for lists, with varying degrees of flexibility.

Conclusions

No conclusions today. A smaller group will begin a deep dive into the space to flesh out the various questions and components of this proposal, and come back to LDM with more research in the area.