
# C# Language Design Notes for Oct. 28, 2019

## Agenda

1. Discard parameters in lambdas and other methods
1. Enhancing the common type algorithm

## Discussion

### Discard parameters

We wanted to talk about discard parameters in lambdas, and also potential expansions into other
parameter lists like in local functions and regular methods.

The first hurdle is whether any form of this feature is worth the complexity. The simplest case
is lambdas, because the parameter names are not visible to the caller so they are only visible to
the implementation. The alternative to discards is to give throw-away names, like
`_1`, `_2`, and `_3`, or to use anonymous method syntax (`delegate { }`) to ignore
all parameters.

As for value, this is a fairly commonly requested requested feature ever since we introduced
discards. The cost is increased complexity in the language (understanding that discards are legal
as lambda parameters), but there's also an argument that not having the feature causes more
complexity in the language. Specifically, the anonymous method syntax is almost entirely obsolete
compared to the lambda syntax, but is still commonly used in this exact scenario. If usage of
this feature decreases the usage of anonymous method syntax, that could be a decrease in required
understanding of the language.

For discards in local functions and method parameter lists, the cost/value ratio is not nearly as
clear. The fundamental limitation is that parameter names for methods and local functions are
always public surface area to the caller. We find the cost in complexity in resolving these
questions higher than the feature is currently worth. If we find that it becomes a highly desired
feature later, we would reconsider this decision.

Lastly, we had a question of how the scoping would work regarding `_` in both
the enclosing scope and inner scope of lambdas.

```C#
void M()
{
    int _ = 0;
    Action<int, int> a = (_, _) =>
    {
        _ = 1; // Is this a discard, or does it capture the local above?
    };
}
```

We considered various options, like making `_` always be a discard in a lambda body when the
lambda parameters are discards, but we have a different precedent for non-lambda
scopes like the following:

```C#
void M()
{
    int _ = 0;
    {
        _ = 1; // This assigns to the variable _
    }
}
```

We decided a better alternative to making complex scoping rules to prevent confusing code is to
push for a warning wave to make using `_` as an identifier a warning. Essentially, if the
above behavior is confusing, the confusing aspects are best resolved by always using `_` as a
discard, rather than special language rules.

The scoping behavior is confirmed that multiple `_`s in a lambda parameter list are discards.
There are no other modifications to variable scopes i.e., they are not introduced in the lambda
body scope, but they also hide nothing from the enclosing scope).

### Common Type Specification

Proposal: https://github.com/dotnet/csharplang/issues/2823

This is revisiting a previous design question around whether to improve the common type
specification and also to target type various expressions. In the last discussion we wanted more
investigation on the impacts of adding target-typing and the consequences to improving the common
type algorithm in the future.

After investigation, there's a proposal to resolve questions about changing common type inference
by looking back to an earlier rule: never infer a type that was not one of the input types. This
is a rule that mainly comes from where to draw the line on complexity of type inference.

This is a simple rule, but it's arguable that there are certain enhancements that don't open up
to question the entire space of inference, but still satisfy simple requests, like assuming that
null and simple integer types can be inferred as nullable. This would be similar to the language
specification for nullable lifting operations on binary operators.

However, that still leaves the fundamental problem we approached in the beginning: improving
inference is a breaking change after target-typing is introduced. The proposal introduced is to
not improve type inference in the future and consider this an acceptable outcome, given that
target typing would satisfactorily resolve most of the examples given, and potentially in a
clearer way than improving the common type algorithm. This would also have the property of
preserving the original constraint of the common type algorithm, where no type is inferred that
isn't present in any of the inputs.

The biggest drawback here is that the switch expression and conditional expression would behave
differently. The conditional expression would have to preserve the common type algorithm for all
places it succeeds, for backwards compatibility.

One possible way out of this is to separate the notion of common type for backwards
compatibility, namely in overload resolution, and the inferred type, as the type used for `var`,
where no target type is available. If feasible, this would resolve the issue mentioned at the
previous meeting, where we would be unable to improve type inference without creating breaking
changes in overload resolution.

**Conclusion**

Overall, we're in favor of adding target-typing for these expression forms. We should consider if
there's anything more we would like to do to make the switch and conditional expressions more
similar.