Finish notes

This commit is contained in:
Mads Torgersen 2017-03-27 08:07:36 -07:00
parent f639a8d2cf
commit e394ac3723
2 changed files with 73 additions and 81 deletions

View file

@ -1,124 +1,109 @@
# C# Language Design Notes for Feb 28, 2017
***Raw notes, yet to be cleaned up - read at your own peril***
"I don't have time to dislike your proposal, but I do!"
*Quote of the Day:* "I don't have time to dislike your proposal, but I do!"
## Agenda
Ensure design for candidate C# 7.1 features
1. Conditional operator over refs (*Yes, but no decision on syntax*)
2. Async Main (*Allow Task-returning Main methods*)
# ref ternary
# Conditional operator over refs
[github.com/dotnet/csharplang/issues/223](https://github.com/dotnet/csharplang/issues/223)
Choice between two variables is not easily expressed, even with ref returns.
If array `a1` is non-null, you want to modify its first element, otherwise you want to modify the first element of `a2`:
``` c#
ref int x = ref If(arr1 != null, ref arr[0], ref arr2[0]); // Wrong!
ref int x = ref (arr1 != null ? ref arr[0] : ref arr2[0]); // Right!
(arr1 != null ? ref arr[0] : ref arr2[0]) = 5;
Choose(a1 != null, ref a1[0], ref a2[0]) = value; // Null reference exception
```
Alternative proposal: If the two branches are already variables of the same type, then the result of a ternary is also a variable, and can be ref'ed.
It would be nice if the ternary conditional operator just worked with refs in the branches:
``` c#
ref int x = ref arr1 != null ? arr1[0] : arr2[0]; // shorter, but ref confusing
ref int x = ref (arr1 != null ? arr1[0] : arr2[0]); // different precedence?
(arr1 != null ? arr1[0] : arr2[1]) = 5; // sure
(a1 != null ? ref a1[0] : ref a2[0]) = value; // Right!
```
Readonly needs to be tracked.
Both branches would have `ref`, and would need to evaluate to variables of the same type.
Order of evaluation: Spec says take location, then evaluate rhs and assign. Compiler evaluates rhs first.
## Syntax
With the syntax as proposed, there's a concern that there will be an abundance of `ref` keywords in common code, leading to confusion. For instance, to store the selected variable above in a ref local would be:
``` c#
a[i] = e; // e happens first
(c ? a[i] : a[i]) = e; // ternary would happen first
ref int r = ref (a1 != null ? ref a1[0] : ref a2[0]); // Four "ref"s
```
Probably not a problem in practice.
Alternative proposal: Do not add new syntax for this. Instead, if both branches of an existing conditional expression are variables of the same type, let the compiler treat the conditional expression as a whole as a variable:
Field like events: should this work for that? Could probably work.
``` c#
(a1 != null ? a1[0] : a2[0]) = value;
ref int r = ref (a1 != null ? a1[0] : a2[0]); // Two "ref"s
```
## Conclusion
- Yes to doing it
- Syntax: no refs inside (recursively)
- Precedence not an issue - `ref` is not part of the expression
- Field like events: sure, if it makes sense
Problem:
This does have a problem that it would subtly change semantics of existing code. Think of a value type `S` that has a mutating method `M`:
``` c#
(b ? v1 : v2).M();
```
If v1 and v2 are value type variables, this will now mutate original instead of copy. Good breaking change? Stopgap?
This would now mutate the *original* `v1` or `v2` of type `S`, instead of a copy! In practice such code today is probably buggy: why would you want to call a method to mutate a throw-away copy? But the fact remains that this would be a breaking change, unless we think up some very insidious mitigation.
An example of such a scheme would be to decide that a conditional is a variable, not based on what's *inside* it, but on what's *around* it. Whenever it's being used as a variable (
An approach is to define variable-ness of ternaries not from the inside out, but by "reinterpreting" it whenever it occurs in variable contexts not allowed today (ref, assignment).
``` c#
(ref b ? v1 : v2).M();
```
## Readonly and safe-to-return
This
This should also work for ["ref readonly"](https://github.com/dotnet/csharplang/issues/38), if and when that goes in. For that to work, we'd either need to require further syntax (`ref readonly` in the branches) or infer readonly-ness from the branches. The latter is more appetizing. We could:
# async Main
1. require both or neither branch be readonly
2. infer that the conditional expression is readonly if at least one branch is readonly
Let's do 2. There seems to be no good reason for a stronger restriction.
Similarly we need to establish whether the resulting ref is safe-to-return. We can infer that the resulting ref is safe-to-return if both branches are safe-to-return.
## Order of evaluation
There are subtle differences between the current spec and implementation when it comes to order of evaluation of an assignment. We need to make sure we fully understand how that plays in when the left hand side of an assignment is a conditional expression.
## Conclusion
- Yes to doing it
- Syntax: leaning towards no refs inside (recursively), but worried about breaking changes
- Precedence not an issue - `ref` is not part of the expression
- Field like events: sure, if it makes sense
# Async Main
[github.com/dotnet/csharplang/issues/97](https://github.com/dotnet/csharplang/issues/97)
We want to allow program entry points (`Main` methods) that contain `await`. The temptation is to allow the `async` keyword on existing entry points returning `void` (or `int`?). However, that makes it *look* like an `async void` method, which is fire-and-forget, whereas we actually want program execution to wait for the main method to finish. So we'd have to make it so that if the method is invoked as an *entry* point we (somehow) wait for it to finish, whereas if it's invoked by another method, it is fire-and-forget.
That is not ideal. Instead, we should allow new entry points returning `Task` and `Task<int>`:
``` c#
static async void Main()
{
await TestAsync(3);
await TestAsync(5);
await Task.WhenAll(TestAsync(3), TestAsync(5));
}
Task Main();
Task Main(string[] args);
Task<int> Main();
Task<int> Main(string[] args);
```
This is not like async void, because it does not return immediately.
Instead we should make it return `Task`
Since such methods can exist today, but are not entry points, we should give precedence to existing entry points for backwards compatibility.
These new new entry points are then entered as if being called like this:
``` c#
static async Task __MainAsync()
{
await TestAsync(3);
await TestAsync(5);
await Task.WhenAll(TestAsync(3), TestAsync(5));
}
static async void Main() => await __MainAsync();
[EntryPoint] static void __Main() => __MainAsync().GetAwaiter().GetResult();
Main(...).GetAwaiter().GetResult();
```
Anyone who calls the `Main` method will get proper async void behavior, but the entry point version waits for completion.
In other words, they rely on the built-in capability of `Task` awaiters to block on the getting the result.
Allow `MainAsync` as a name?
Allow `async Task Main()`? Yes!
Allow `Task Main()`? Yes
C# 7.0 allows declaration of other "tasklike" types. We won't allow those in entry points yet, but that is easy to work around: Just `await` your tasklike in an `async Task Main`.
## Conclusion
SHould we *dis*allow `async void Main`, on the grounds that async void is bad, and make it about `Task Main` (`async` optional). If we get a ton of complaints, we can add it later. We'll disallow `async void Main` as an entry point to start with, knowing that it may yield some feedback.
Allow `Task<int>`? Yes.
Allow other tasklikes? No. Not for now. Very little value. Just await your tasklike in an `async Task Main`.
# "default" expression
# Field-targeted attributes on auto-properties
# Better "common type" algorithm
# private protected
# discards for lambda parameters
# Mix declarations and variables in deconstruction
# permit match of expr of type T:base with derived
# allow expression variables in field and constructor initializers
Allow `Task` and `Task<int>` returning `Main` methods as entry points, invoking as `Main(...).GetAwaiter().GetResult()`.

View file

@ -55,3 +55,10 @@ As part of this we revisited potential 7.1 features and pushed several out.
We went over the proposal for `ref readonly`: [Champion "Readonly ref"](https://github.com/dotnet/csharplang/issues/38).
## Feb 28, 2017
[C# Language Design Notes for Feb 28, 2017](LDM-2017-02-28.md)
1. Conditional operator over refs (*Yes, but no decision on syntax*)
2. Async Main (*Allow Task-returning Main methods*)