csharplang/meetings/2019/LDM-2019-03-19.md
2019-04-03 22:46:48 -07:00

6.8 KiB

C# LDM Notes for Mar 19, 2019

Agenda

MVP Summit Live LDM w/ Q&A

Topics:

  1. Records
  2. "Extension interfaces"/roles
  3. Macros
  4. IAsyncEnumerable
  5. "Partially automatic" properties
  6. More integration with reactive extensions

Discussion

We grouped the discussion into topics to try to maintain a bit of shared state in the discussion.

Records

Q: How "extensible" will the design be? Could the user provide some sort of template that the compiler would implement for their record, more than the equality and data access features we are initially thinking of?

We're not clear on what kind of templating we could offer, but there are two problems we foresee with generalizing the feature:

  1. Even simple equality is actually a bit tricky to write and be correct for subtyping.
  2. Generalized templating is tricky to do in a type safe way. One alternative is something like hygenic macros, but we're not willing to go that far in the language at the moment. We definitely are not interested in allowing for custom syntax forms.

Q: Do we not want inheritance? If that's the only thing blocking records?

  1. Inheritance isn't the main thing blocking records. It's mostly lack of design time to work out the last few issues, namely what the simplest syntax means, support for nominal and structural construction, and incorporating the design with discriminated unions.

  2. We think inheritance is important for "extensible" records and it's hard to add later.

Q: Will record syntax be supported for structs?

Yes, almost certainly. We're also looking into discriminated unions for structs as well, although it's more difficult because the size of the struct is more complicated.

Q: Deep vs shallow immutability? Value-equality is more natural if there's deep immutability, since two things which may be value equal originally may not be so if a nested value is changed, but the top level equality is only calculated shallowly.

Not currently on the table, records will only have shallow immutability be default, if they are immutable by default. We probably have no mechanism to figure out if equality is broken, either, because the language doesn't currently have a good way of reasoning about this transitevely.

Extension interfaces/roles

Q: Is this duck typing?

Maybe, it depends on what you mean by duck typing. It's certainly a way to implement an interface after the type has been defined. One of the design goals can be phrased, "here's a class, here's an interface, let's make them fit together", but it's not restricted to what's stated in the class, because we feel there's often a small gap between the interface and class that needs to be filled out.

It's also not the case that any type which structurally matches the interface "magically" implements the interface -- all our current designs are nominal and explicit. This is partially because it fits with how C# works as a whole, but also that interface implementation is a CLR language concept that isn't as loose as some other duck typing designs.

Q: Constructors on interfaces?

(Meaning no body, just for the constraint)

It's not currently a part of "static members on interfaces" and it seems very similar to proposals to extend the new() constraint?

When we looked at it in the past we thought the cost/value prop is probably questionable as a standalone feature, but maybe as part of the broader interface feature. The biggest mark against it is there's a workaround of using a delegate that returns the given interface, which generally works pretty well.

Q: What scope would the "extension interfaces" be in?

We'll probably import most of the scoping rules of extension methods.

Q: What about extension fields? Using ConditionalWeakTable?

This consequences are subtle and we'll probably favor having the user do this explicitly if that's what they mean.

Q: Scala-like "implicit" parameters?

We looked at it, but we're concerned it may be too subtle.

General metaprogramming

Q: Source generators?

We tried a maximal approach for source generators, but the tooling was not able to keep up. There may be something here, but we're not going to do anything until we can guarantee investment across the whole stack.

Q: Can we skip the tooling?

There isn't really a difference between generated C# and standard C#, so there's no clear line to "cut off" the tooling. It's not the author that we're primarily worried about, it's all of the downstream users, who are strongly expecting current features to continue to work.

Q: What are new languages the LDM looks to for ideas?

We generally look at everything we can find, and the LDM has people with their own interests, so we tend to have a mix of perspectives.

Some examples:

We've looked at Rust and Go for perf-centric designs and "zero-cost abstraction" ideas.

We look at Scala for lot's of ideas on merging functional and OO. They're not necessarily the language we want to be, but they've traveled a lot of the same ground.

IAsyncEnumerable

Q: How does ConfigureAwait/Cancellation work?

Right now we have ConfigureAwait and WithCancellation methods for IAsyncEnumerable that allow you to add functionality at the await foreach site, e.g.

await foreach (var x in iasyncenum.ConfigureAwait(false))

What we don't currently have a mechanism for is flowing a token from the foreach to the generated awaits in the iterator method. For instance,

var iasyncenum = IterMethod(token);
await foreach (var x in iasyncenum)
{
    ...
}

IAsyncEnumerable<int> IterMethod(CancellationToken token)
{
    yield return 0;
    token.ThrowIfCancelled();
    await Task.Delay(1);
    yield return 1;
}

Note that you can flow a token through manually, but then you must control calling the iterator and executing the foreach -- there's no way to pass a new cancellation token during the await foreach.

There's some feedback that this could be useful, so we'll take another look at it.

Q: Plans to add an ability to await an event?

We're not quite sure how this would work. In general, libraries have some support for this pattern, so we don't see a huge need right now.

Q: Plans to support "partially automatic" properties that let you access the generated backing field?

There's been some talk about allowing a field to be declared inside the property that's only accessible there, e.g.

public int Property
{
    private int _field;
    get => _field;
    set { _field = value; }
}

We haven't had a full discussion about this idea yet.

More integration with Reactive Extensions in the language?

For instance, IObservable?

Our current view is that IAsyncEnumerable is the mechanism to link Rx into the language. The problem with stronger language integration is that it will require configuration for various buffering/back-pressure options. We think that's better solved by library authors and hope that the current design will strike a good balance.