Add LDM notes for 2018-11-28
This commit is contained in:
parent
877367288a
commit
0a4aa03e37
188
meetings/2018/LDM-2018-11-28.md
Normal file
188
meetings/2018/LDM-2018-11-28.md
Normal file
|
@ -0,0 +1,188 @@
|
|||
|
||||
# C# LDM notes for Nov 28, 2018
|
||||
|
||||
1. Are nullable annotations part of array specifiers?
|
||||
2. Cancellation of async-streams
|
||||
|
||||
# Discussion
|
||||
|
||||
## Nullable array specifiers
|
||||
|
||||
Due to history, the syntax for array specifiers is actually "outside-in",
|
||||
i.e. the first set of `[]` actually refers to the outermost array. For the
|
||||
elements themselves, you always specify the `?` directly after the element
|
||||
type name, so `string?[][]` works regardless of interpretation.
|
||||
|
||||
For specifying the nullability of the "innermost" vs "outermost" arrays, it
|
||||
seems like we have two possibilities for describing nullability here:
|
||||
|
||||
1. `string[]?[] a`
|
||||
2. `string[][]? a`
|
||||
|
||||
Either of these could specify that the *innermost* array is null. Here are full
|
||||
examples of the meaning of approaches (1) and (2) with a type declaration:
|
||||
|
||||
```
|
||||
string?[][] a; new string?[3][] // 1. Array nullable string, 2. likewise
|
||||
string[]?[] a; // 1. Nullable array of array of string, 2. Array of nullable array of string
|
||||
string[][]? a; // 1. Array of nullable array of string, 2. Nullable array of array of string
|
||||
```
|
||||
|
||||
If we look at instantiation, rather than type declaration, the two approaches look
|
||||
like:
|
||||
|
||||
1.
|
||||
|
||||
```
|
||||
new string?[3][]
|
||||
new string[3]?[] // invalid
|
||||
new string[3][]?
|
||||
```
|
||||
|
||||
|
||||
2.
|
||||
|
||||
```
|
||||
new string?[3][]
|
||||
new string[3][]? // invalid
|
||||
new string[]?[3]
|
||||
```
|
||||
|
||||
There's also one more option that's a hybrid. Because of array covariance and
|
||||
nullable covariance, assigning an array of non-nullable arrays to an array of
|
||||
nullable arrays does not generate warnings. Declarations would like (2), but
|
||||
instantiation would actually disallow `?` completely:
|
||||
|
||||
3.
|
||||
|
||||
```
|
||||
string?[][] a = new string?[3][];
|
||||
// string[]?[] a = new string[]?[3]; // disallow ? in new
|
||||
string[]?[] a = new string[3][];
|
||||
string[][]? a = new string[3][];
|
||||
```
|
||||
|
||||
There are essentially two mental models of multi-dimensional arrays that have
|
||||
served people writing multi-dimensional array code: the way the code actually
|
||||
works, and a "type nesting" model that is mostly non-observable in the
|
||||
current language. When choosing between options 1 and 2/3, we have a decision
|
||||
to either preserve how multi-dimensional arrays actually "work", regardless
|
||||
of whether or not it's confusing, or try to accommodate how we might prefer
|
||||
they work and how people may actually think they work.
|
||||
|
||||
Option (1) is attractive not only because it's how the feature currently
|
||||
works, but also because Option 2/3 would involve changing the order of the
|
||||
rank specifier specifically for nullable multi-dimensional arrays, so code
|
||||
that previously used `new string[3][]` would now be written `new
|
||||
string[]?[3]`, changing not just the location of the `?`, but also the
|
||||
location of `3`.
|
||||
|
||||
One reason to go with accommodation is to make nullability specifically more
|
||||
similar to how it works in other places in the language. Generally, to make a
|
||||
type nullable, you add a `?` to the end. This would not be the case with the
|
||||
existing multi-dimensional array model.
|
||||
|
||||
If most people are adopting existing code with jagged arrays, Option 2/3 may
|
||||
make it easier to adopt the rules common in other areas of the language to
|
||||
make the warnings go away. Since we don't provide nullability warnings for
|
||||
everything but assigning null as the innermost array, the nullability of the
|
||||
innermost array matters a little less.
|
||||
|
||||
|
||||
**Decision**
|
||||
|
||||
Let's stick with the current implementation (1). People may have incorrect
|
||||
mental models which have served them sufficiently until now, but we don't
|
||||
yet see sufficient motivation for complicating the language.
|
||||
|
||||
|
||||
## Cancellation of async streams
|
||||
|
||||
### CancellationToken in the IAsyncEnum* interfaces
|
||||
|
||||
The first question is what kind of support for cancellation we want to put
|
||||
into the interface itself. The primary proposal here is changing the
|
||||
signature of `IAsyncEnumerable<T>` to take an optional CancellationToken:
|
||||
|
||||
```C#
|
||||
IAsyncEnumerable<T>
|
||||
{
|
||||
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token = default);
|
||||
}
|
||||
```
|
||||
|
||||
The user's implementation of the interface could then provide a mechanism to
|
||||
pass a CancellationToken during construction, and we could provide an
|
||||
extension method `IAsyncEnumerable<T> WithCancellationToken<T>(this
|
||||
IAsyncEnumerable<T> e, CancellationToken token)` that could wrap an existing
|
||||
`IAsyncEnumerable<T>` with a cancellable one.
|
||||
|
||||
### Async iterators and `await foreach`
|
||||
|
||||
For the new language features there are two potential scenarios to support.
|
||||
|
||||
The first is allowing the user to pass in their own token for consumption
|
||||
i.e., cancellation during an `await foreach`.
|
||||
|
||||
The second scenario is allowing the user to consume a CancellationToken
|
||||
during production i.e., when writing an async iterator.
|
||||
|
||||
#### Consumption
|
||||
|
||||
Most proposals center around some extra syntax for `await foreach` that
|
||||
allows a CancellationToken to be passed e.g.,
|
||||
|
||||
```C#
|
||||
await foreach (var x in e, cancellationToken)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
If we allow specifying the cancellation token during enumeration, passing
|
||||
the cancellation token when calling the generated iterator is probably the
|
||||
wrong place to pass it, as you should put it in through enumeration.
|
||||
|
||||
#### Production
|
||||
|
||||
A user scenario is the user is writing an iterator and wants to react to
|
||||
a cancellation token that can in as a parameter. For example,
|
||||
|
||||
```C#
|
||||
async IAsyncEnumerable<int> M(..., CancellationToken token = default)
|
||||
{ ... }
|
||||
```
|
||||
|
||||
If we're writing an iterator, the main feature we would need to support is
|
||||
putting the CancellationToken into the generated iterator and specifically
|
||||
connect it to a CancellationToken provided to the method.
|
||||
|
||||
One way of supporting this would have a special "value"-like keyword that
|
||||
refers to an implicit cancellation token that's used in the generated
|
||||
enumerable. An even more maximalist proposal would allow implicit
|
||||
CancellationTokens that can be added to the end of *any* method, and if a
|
||||
CancellationToken is passed anywhere then it can be implicitly threaded
|
||||
through. This is a much larger feature.
|
||||
|
||||
Another way of providing a CancellationToken could be specifying that a
|
||||
particular parameter that is the special CancellationToken, e.g.
|
||||
```C#
|
||||
IAsyncEnumerable<int> M(..., [Cancellation]CancellationToken token = default)`
|
||||
```
|
||||
|
||||
Whichever way the custom token is specified, the token would be stored in the
|
||||
`IAsyncEnumerable` state machine, and if another token is passed to
|
||||
`GetEnumeratorAsync` then we would also store that and decide if we create a
|
||||
combined token for both, or override the original token provided.
|
||||
|
||||
**Decision**
|
||||
|
||||
1. Change `IAsyncEnumerable<T>.GetAsyncEnumerator()` to `IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken token = default)` and
|
||||
provide a `WithCancellation` extension method.
|
||||
|
||||
2. In the constructed iterator state machine, `GetEnumerator` should
|
||||
throw if the given CancellationToken is cancelled. It's not decided exactly
|
||||
where or how often we will check for cancellation.
|
||||
|
||||
3. Do nothing more, right now. Revisit the production and consumption sides
|
||||
of this before shipping.
|
|
@ -219,12 +219,14 @@ Discussion of records proposals:
|
|||
1. Base call syntax for default interface implementations
|
||||
2. Switch exhaustiveness and null
|
||||
|
||||
# Upcoming meetings
|
||||
|
||||
## Nov 28, 2018
|
||||
|
||||
- Are nullable annotations part of array specifiers? (Neal, Chuck)
|
||||
- Cancellation of async-streams (Stephen, Julien)
|
||||
[C# Language Design Notes for Nov 28, 2018](LDM-2018-11-28.md)
|
||||
|
||||
1. Are nullable annotations part of array specifiers?
|
||||
2. Cancellation of async-streams
|
||||
|
||||
# Upcoming meetings
|
||||
|
||||
## Dec 3, 2018
|
||||
|
||||
|
|
Loading…
Reference in a new issue