csharplang/meetings/2020/LDM-2020-10-07.md
Fred Silberberg 1efe320de3
Fix ToC
2020-10-07 16:01:51 -07:00

6.1 KiB

C# Language Design Meeting for October 7th, 2020

Agenda

  1. record struct syntax
  2. data members redux
  3. ReadOnlySpan<char> patterns

Quote of the Day

  • "And we're almost there (famous last words, I'll knock on something)"
  • "What about record delegate... does record interface make sense... I'll go away now"

Discussion

record struct syntax

First, we looked at what syntax we would use to specify struct types that are records. There are two possibilities:

record struct Person;
struct record Person;

In the former, record is the modifier on a struct type. In the latter, struct is the modifier on record types. We also considered whether to allow record class.

Conclusion

By unanimous agreement, record struct is the preferred syntax, and record class is allowed. record class is synonymous with just record.

data members redux

With data, we come back to the same question we had at the end of Monday: should we have data members, and if we do, what should they mean. data can be viewed as a strategy to try and get nominal records in a single line, much like positional records. This is not a goal of brevity just for brevity's sake: in discriminated unions, listing many variants as nominal records is a design goal, and single-line declarations are particularly useful for this case. Many languages with discriminated unions based on inheritance introduce short class-declaration syntax for use in union declarations, and that was a defining goal for where record types would be useful.
Another area worth examining is the original proposal for the data keyword. In the original proposal, primary constructors would have meant the same thing in record types as well non-record types, and data would have been used as a modifier on the primary constructor parameter to make it a public get/init property. data applied to a member, then, would have been a natural extension and reuse of that keyword. With the original scenario gone, there are a few concrete scenarios we think are highly related to, and will influence, the data keyword:

  1. Use as a single-line in a discriminated union. While this is motivating, it's worth considering that, at least to some LDT members, anything more than 2 properties as data members doesn't look great, and would perhaps work better as a multi-line construct, which will line up visually. This seems a reasonable concern, so data may not be the solution we're looking for.
  2. Use in required properties, as it seems likely that we will need some keyword to indicate that a property is a required member in a type. While data could be orthogonal to some other required property keyword, it's likely there will be at least some interaction as we'd likely want data to additionally imply required. It's also possible that we could have just a single keyword to mark a property required, and it gives you what we believe are the useful defaults for such a property.
  3. Use in general primary constructors as a way to say "give me the same thing a record would have for this parameter". This would be somewhat resurrecting the original use case for the keyword, as we could then retcon record primary constructors as implying data on all parameters for you, but you can then opt-in to them in regular primary constructors as well. data members in a class, then, would again become a natural reuse and extension of the keyword on a primary constructor parameter.

Conclusion

We'll leave data out of the language for now. Our remaining motivating scenarios are things that will be worked on more in C# 10 cycle, and absent clearer designs in those spaces the need and design factors for data are too abstract. Once we work more on these 3 scenarios, we'll revisit data and see if a need for it has emerged.

ReadOnlySpan<char> patterns

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

We have a community PR to implement the Any Time feature of allowing constant strings to be used to pattern match against a ReadOnlySpan<char>. This would be acknowledging special behavior for ReadOnlySpan<char> with respect to constant strings, but we already have acknowledged special behavior for Span and ReadOnlySpan in the language, around foreach. We also considered whether this could be a breaking change, but we determined it could not be one: ReadOnlySpan cannot be converted to object or to an interface as it is a ref struct, so if there exists a pattern today operating on one today the input type of that pattern match must be ReadOnlySpan<char>. There were two open questions:

Should we allow Span<char>

The question is if Span<char> should also be allowed as well as ReadOnlySpan<char>. All of the same arguments about compat apply to Span<char>. We also considered whether Memory/ReadOnlyMemory should be allowed inputs. Unlike Span/ReadOnlySpan, though, there are backcompat concerns with Memory that cannot be overlooked, as they are not ref structs. It is very easy to obtain a Span/ReadOnlySpan from a Memory/ReadOnlyMemory, however, so the need isn't as great.

Conclusion

Span<char> is allowed. Memory<char>/ReadOnlyMemory<char> are not.

Is this specific to switch, or can it be any pattern context

The original proposal here just mentioned switch. However, the implementation allows it to be used in any pattern context, such as is.

Conclusion

Allowed in all pattern contexts.

Final Notes

We also discussed making sure that switching on a Span/ReadOnlySpan is as efficient for large switch statements as switching on a string is. Over 6 cases, the compiler will take the hashcode of the string and use it to implement a jump table to reduce the number of string comparisons necessary. This method is added to the PrivateImplementationDetails class in an assembly, and we should make sure to do the same thing for Span<char> and ReadOnlySpan<char> here as well so the cost to using one isn't higher than allocations would be from just doing a substring and matching with that.