Add LDM notes for March 30, 2020
This commit is contained in:
parent
26342db77a
commit
0286170d01
112
meetings/2020/LDM-2020-03-30.md
Normal file
112
meetings/2020/LDM-2020-03-30.md
Normal file
|
@ -0,0 +1,112 @@
|
|||
|
||||
# C# Language Design Meeting for March 30, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Records
|
||||
|
||||
1. Builders vs init-only
|
||||
|
||||
2. Possible conflicts with discriminated unions
|
||||
|
||||
3. Value equality
|
||||
|
||||
## Discussion
|
||||
|
||||
### Builders vs. init-only
|
||||
|
||||
We discussed more of the tradeoffs of using builders vs using an "init-only" feature for records,
|
||||
and looked into requirements for other languages, including VB and F#. Based on our current
|
||||
designs, the work needed to consume the new features for "init-only" seem fairly small.
|
||||
Recognizing the `modreq` and allowing access to init-only members, and calling any necessary
|
||||
"validator" on construction, are pretty simple features for VB. VB already has the syntactic
|
||||
requirements for the feature (object initializers), and we'd like to keep it possible for VB to
|
||||
consume major changes in the API surface, if not write those new features. F# is undergoing
|
||||
active development and changes are certainly viable there. Because the scope of changes is more
|
||||
open-ended in F#, it's possible it could feature more implementation work.
|
||||
|
||||
Notably, most of the features associated with "init-only" and "validators" do not require a new
|
||||
runtime, only a new compiler version. The new compiler version is necessary to recognize safety
|
||||
rules (validators must always be called after constructors, if they exist), but they don't use
|
||||
any new features in the CLR. The only feature potentially requiring runtime features is
|
||||
overriding a record with another record, which could potentially require covariant returns.
|
||||
|
||||
The remaining differences seem to come down to whether you can "see" the whole object during
|
||||
initial construction (as opposed to validation). If you can see the whole object immediately,
|
||||
that makes writing a `With` method that avoids a copy if all the values are identical very
|
||||
straightforward. However, this could be done for "init-only" as well, by moving this semantic to
|
||||
the `with` expression, optionally comparing the arguments to the `With` expression with the
|
||||
values on the receiver object and avoiding calling With if they are identical. Therefore we don't
|
||||
think we're actually ruling anything out by going down the "init-only" route.
|
||||
|
||||
There are advantages in going down the "init-only" route instead. The performance for structs
|
||||
is probably better and more optimizable, and the IL pattern seems clearer and less bloated.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
We like the "init-only" IL better. Given the path forward for other languages and compatibility
|
||||
with many runtimes, we think it's a better future as an IL pattern.
|
||||
|
||||
### Conflicts with discriminated unions
|
||||
|
||||
There was a general question if these decisions could impact a future discriminated unions feature.
|
||||
We don't have a lot in mind, but if we do end up building a discriminated union made of the records
|
||||
feature, there is one component we may want to reserve. Discriminated unions often have a set of simple
|
||||
data members. For instance, if we wrote a Color enum as a discriminated union, it could look like.
|
||||
|
||||
```C#
|
||||
enum class Color
|
||||
{
|
||||
Red,
|
||||
Green,
|
||||
Blue
|
||||
}
|
||||
```
|
||||
|
||||
If we reduce these to records, it may look like:
|
||||
|
||||
```C#
|
||||
abstract class Color
|
||||
{
|
||||
public class Red() : Color;
|
||||
public class Green() : Color;
|
||||
public class Blue() : Color;
|
||||
}
|
||||
```
|
||||
|
||||
The problem is: since those classes are effectively singletons, we'd like for the instances to be
|
||||
cached by the compiler, to avoid allocations. However, we don't currently have a syntax in C# that
|
||||
means "singleton." Scala uses the "empty record" syntax to mean singleton. We need to decide if we'd
|
||||
like to reserve that syntax ourselves, or find some other solution.
|
||||
|
||||
### Value equality
|
||||
|
||||
We've decided that we want value equality by default for records. We need to settle on what that
|
||||
means. The primary proposal on the table is shallow-field-equality, namely comparing all fields
|
||||
in the type via EqualityComparer. This would match the semantic we have decided on for "Withers,"
|
||||
where the shallow state of the object is copied, similar to MemberwiseClone.
|
||||
|
||||
There's a large segment of the LDM that thinks doing anything except for field comparison is
|
||||
problematic because it introduces far too many customization points for the record feature.
|
||||
Almost all custom equality would have to deal with sequence equality and string equality, which
|
||||
are already very different mechanisms. There's a proposal that we could provide further
|
||||
customization via source generators, which could allow almost any customization.
|
||||
|
||||
A follow-up to that is: why have value equality by default at all? Why not use source generators
|
||||
for all value equality? One problem with that is that we want the simplest records to be very
|
||||
short. The other problem is that we effectively have to pick an equality (C# will inherit one if
|
||||
you don't). We previously decided that value equality is a non-controversial default -- if it's
|
||||
wrong it's probably not worse than when reference equality is wrong, and it's often better.
|
||||
Struct-like field-based equality is a simple and familiar version of value equality.
|
||||
|
||||
We also need to decide on value-equality as a separable feature for regular classes. Some people
|
||||
like the idea of a separate feature and would be fine even with the constrained version that only
|
||||
compares fields. Others don't think this feature meets the hurdles for a new language feature. If
|
||||
we don't have customization options, it may be rarely used, and it seems possible that a source
|
||||
generator version could be the much more popular version. It's also worth noting that, as a
|
||||
separable feature, we don't need to add separable equality now.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
No separable value equality for non-records, right now. Default record equality is defined as
|
||||
field-based shallow equality.
|
|
@ -36,10 +36,6 @@
|
|||
- https://github.com/dotnet/roslyn/issues/39865 Function pointer calling conventions (frsilb)
|
||||
- https://github.com/dotnet/csharplang/issues/140 `field` keyword in properties (cyrusn)
|
||||
|
||||
## March 30, 2020
|
||||
|
||||
- Record Monday (Andy, Mads)
|
||||
|
||||
## March 18, 2020
|
||||
|
||||
- https://github.com/jaredpar/csharplang/blob/record/proposals/recordsv3.md clone-style records (Jared)
|
||||
|
@ -60,6 +56,12 @@
|
|||
|
||||
Overview of meetings and agendas for 2020
|
||||
|
||||
## March 30, 2020
|
||||
|
||||
1. Record Monday
|
||||
|
||||
[C# Language Design Notes for March 30, 2020](LDM-2020-03-30.md)
|
||||
|
||||
## March 25, 2020
|
||||
|
||||
[C# Language Design Notes for March 25, 2020](LDM-2020-03-25.md)
|
||||
|
|
Loading…
Reference in a new issue