Add meeting notes for Jan. 29, 2020
This commit is contained in:
parent
013d92093d
commit
4914e41489
106
meetings/2020/LDM-2020-01-29.md
Normal file
106
meetings/2020/LDM-2020-01-29.md
Normal file
|
@ -0,0 +1,106 @@
|
|||
|
||||
# C# Language Design for Jan. 29, 2020
|
||||
|
||||
## Agenda
|
||||
|
||||
Record -- With'ers
|
||||
|
||||
## Discussion
|
||||
|
||||
In this meeting we'd like to talk about Mads's write-up of the "With-ers" feature, as it relates
|
||||
to records. Multiple variations have been proposed, but the suggestion generally takes the form
|
||||
of a `with` expression that can return a copy of a data type, with selective elements changed.
|
||||
|
||||
Write-up: https://github.com/dotnet/csharplang/issues/3137
|
||||
|
||||
The first thing we learned is that the fundamental problem we're trying to solve is
|
||||
"non-destructive mutation."
|
||||
|
||||
There are two approaches we've thought of: direct copy and then direct modification, and creation of a new type based on the values of the old type.
|
||||
|
||||
1. Direct copy. We might call this "copy-and-update" because we copy the new data type exactly,
|
||||
then update the new type with required changes. The basic implementation would be to use
|
||||
MemberwiseClone, and then overwrite select properties.
|
||||
|
||||
2. Create a new type. we call this "constructing through virtual factories." If the type supports
|
||||
a constructor, this approach would call the constructor using the new values, or the existing
|
||||
ones if nothing new is given. The construction would be virtual so that derived types would not
|
||||
lose state when called through the base type.
|
||||
|
||||
There are advantages and disadvantages to each proposal.
|
||||
|
||||
(1) is simple but seemingly dangerous. There are often internal constraints to a type which must
|
||||
be preserved for correctness. Usually this is enforced through the type constructor and
|
||||
visibility of modification. That would not necessarily be available here.
|
||||
|
||||
(2) does construction similar to conventional construction today, so it doesn't introduce as many
|
||||
safety concerns. On the other hand, the contract looks a lot more complicated. To make the
|
||||
feature seem simple on the surface, it looks like we imply a lot of implicit dependency. For
|
||||
example,
|
||||
|
||||
```C#
|
||||
public data class Point(int X, int Y);
|
||||
var p2 = p1 with { Y = 2 };
|
||||
```
|
||||
|
||||
Would generate
|
||||
|
||||
```C#
|
||||
public class Point(int X, int Y)
|
||||
{
|
||||
public virtual Point With(int X, int Y) => new Point(X, Y);
|
||||
}
|
||||
var p2 = p1.With(p1.X, 2);
|
||||
```
|
||||
|
||||
The first requirement is that an auto-generated `With` method must have a primary constructor, in
|
||||
order to know which constructor to call. Alternatively, we could have a `With` method generated
|
||||
for every constructor, although that would require a syntax to signal that `With` methods should
|
||||
be generated in the absence of a primary constructor.
|
||||
|
||||
The compiler also needs to know that the `X` and `Y` parameters of the `With` method correspond
|
||||
to particular properties, so it can fill in the defaults in the `with` expression. Otherwise we
|
||||
would need some way of signifying which of the parameters are meant to be "defaults":
|
||||
|
||||
```C#
|
||||
public class Point(int X, int Y)
|
||||
{
|
||||
public virtual Point With(bool[] provided, int X, int Y)
|
||||
{
|
||||
return new Point(provided[0] ? X : this.X, provided[1] ? Y : this.Y);
|
||||
}
|
||||
}
|
||||
var p2 = p1.With(new bool { false, true}, default, 2);
|
||||
```
|
||||
|
||||
We also need to figure out which `With` method to call at a particular call site. One way is to
|
||||
construct an equivalent call and perform overload resolution. Another way would be to pick a
|
||||
particular `With` method as primary, and always use that one in overload resolution.
|
||||
|
||||
This also has some of the same compatibility challenges that we've seen in other areas.
|
||||
Particularly, if you add members to the record, there will be a new `With` method with a new
|
||||
signature. This would break existing binaries referencing the old `With` method. In addition, if
|
||||
you add a new `With` method, the old one would still be chosen by overload resolution, if
|
||||
overload resolution is performed, as long as unspecified properties in the `with` expression are
|
||||
default values.
|
||||
|
||||
On the other hand, this is also a general problem with backwards compatibility overloads. We'll
|
||||
need to investigate whether we want to add a general purpose mechanism for handling backwards
|
||||
compatibility and if we want to introduce a special case for With-ers specifically.
|
||||
|
||||
What all of the above interdependency implies is that we need a significant amount of syntax or
|
||||
"hints" about what to do during autogeneration. We previously expressed interest in providing
|
||||
orthogonality for as many of the "record" features as possible. A conclusion is that
|
||||
auto-generated With-ers require or suggest many of syntactic and semantic components of records
|
||||
themselves. When we try to separate the feature entirely, we require user opt-in to specify the
|
||||
"backing" state of the With-er. This seems to imply that auto-generation should
|
||||
not be a general, orthogonal feature, but a specific property of records.
|
||||
|
||||
However, we don't have to give up orthogonality entirely. The requirements for auto-generated
|
||||
With-ers doesn't imply anything about manually written With-ers. Auto-generation seems possible
|
||||
in records because the syntax ties the state to the public interface. Manual specification looks
|
||||
just like the components of records that can be written explicitly in regular classes, like
|
||||
constructors themselves. If we do want to pursue this avenue, we should try to limit
|
||||
the complexity of the pattern as much as possible. It's not too bad if it's fully
|
||||
generated by the compiler, but it can't be very complicated if we want users to write
|
||||
it themselves.
|
|
@ -32,8 +32,6 @@
|
|||
|
||||
- https://github.com/dotnet/csharplang/issues/3137 Records (Mads)
|
||||
|
||||
## Feb 5
|
||||
|
||||
## Feb 3
|
||||
|
||||
- https://github.com/dotnet/csharplang/issues/3137 Records as individual features (Mads)
|
||||
|
@ -50,11 +48,19 @@
|
|||
|
||||
Overview of meetings and agendas for 2020
|
||||
|
||||
## Feb 5
|
||||
|
||||
[C# Language Design Notes for Feb. 5, 2020](LDM-2020-02-05.md)
|
||||
|
||||
- Nullability of dependent calls (Chuck, Julien)
|
||||
- https://github.com/dotnet/csharplang/issues/3137 Records as individual features (Mads)
|
||||
|
||||
## Jan 29, 2020
|
||||
|
||||
[C# Language Design Notes for Jan. 29, 2020](LDM-2020-01-29.md)
|
||||
|
||||
Records: "With-ers"
|
||||
|
||||
## Jan 22, 2020
|
||||
|
||||
[C# Language Design Notes for Jan 22, 2020](LDM-2020-01-22.md)
|
||||
|
|
Loading…
Reference in a new issue