Back to Csharplang

C# Language Design Meeting for November 10th, 2021

meetings/2021/LDM-2021-11-10.md

latest5.2 KB
Original Source

C# Language Design Meeting for November 10th, 2021

Agenda

  1. Self types

Quote of the Day

  • "Is that a threat [redacted]?" "I think it's a promise" "Well, then someone gets to tell management why it slipped." "Now that was a threat."

Discussion

Self types

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

Today, we took a first look at adding a self type constraint to C#, as part of the work on generic math in C#. We see 3 main problems that the self type constraint could address:

  1. Today, generic math using the Curiously Recurring Type Pattern (CRTP) to specify self types, which looks like interface INumber<T> where T : INumber<T>. This is quite verbose, requiring stating the name of the interface twice, and even a small reduction to interface INumber<T> where T : this helps both with the number of characters typed and the clarity of the intent of the code.
  2. CRTP does not actually enforce that the T implemented must be the current type. For example, someone could do struct MyStruct : IAdditiveIdentity<int>, because int implements IAdditiveIdentity<int>. CRTP involving operators will naturally enforce that T is actually the current type, because of the rules around operator implementation, but this is complex from both a spec perspective and an implementation perspective.
  3. Being able to call explicit implementations of an interface. In .NET 6, many of the preview interface definitions are implemented on types explicitly, to avoid users accidentally taking a dependency on them without meaning to. However, this leads to confusion with how to call that implementation: you have to create a generic wrapper method, which we've received feedback is quite confusing. While some of these explicit implementations will be transitioned to regular implementations in .NET 7 when the feature ships for real, a few must remain explicit to avoid covering existing functionality that behaves in a slightly different manner.

Of these concerns, we think the best motivation for self types is actually the first one. For concern 2, we're unsure what the real harm is. Yes, users can provide an odd implementation of one of these interfaces, but that's going to be true forever in C# regardless of our decision here, unless we take a breaking change to IEquatable<T> and similar interfaces that shipped with C# 2. It does simplify some rules for the compiler, but ultimately we're not sure that addressing concern 2 is a motivating factor.

Concern 3 is valid, but we're not sure the self type is the appropriate way to fix it. We've had base calls for DIMs on the backlog for a few years now, and we see the possibility for a more generalized way to call a specific implementation of a method on a type. The current design for the self type happens to address this concern, but not in a generalized fashion. In addition, several members of the LDM are not convinced by the prevalence of the need to call these implementations once .NET 7 is out: they do exist, but how common will needing those implementations be?

We also talked about the possibility of using associated types to represent the self type, rather than a public type parameter. While this is somewhat attractive, we're in general agreement that unless we have real runtime support for an associated self type, we don't want to take this direction. In C#, we generally avoid having metadata significantly differ from the source; if the metadata needs to have a generic type parameter, then we would like the source to indicate such. At some point, associated types need to be translated into the universal type space, which will spread virally throughout the program: that either needs to be done at compile time by Roslyn (the representation change we want to avoid), or it needs to be done by the runtime. The runtime has concerns about avoiding an MxN problem (where M methods will need to be specialized N times) without whole-program analysis, which is not always available. There's also some argument that, of all the things associated types can do, the self type is one of the least interesting ones. Consumers of the generic math feature will almost certainly still need to use a generic type parameter, because unless they're creating a method with one input and no outputs, they will need to constrain each input and output to be the same associated type, not just some associated self type. IE, using a hypothetical INumber.this syntax:

cs
// How is the compiler to know that the `INumber.self` of the `left` parameter is the same as the `INumber.self` of the `right` parameter, and how does it make
// any assumptions about the return type?
public INumber.self Add(INumber.self left, INumber.self right) => left + right;

// Users would actually need to resort to generics to relate these parameters:
public T Add<T>(T left, T right) where T : INumber.self => left + right;

Given that we see the vast majority of the usages of the self type being consumption, not definition, we're unsure the potentially significant cost of the associated->universal type translation actually buys the user anything.

Conclusions

We're going to have a small group explore this space more and make revisions to the proposal before coming back to LDM.