Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Proposal]: Directly invoked anonymous functions #8869

Closed
MadsTorgersen opened this issue Dec 12, 2024 · 0 comments
Closed

[Proposal]: Directly invoked anonymous functions #8869

MadsTorgersen opened this issue Dec 12, 2024 · 0 comments
Assignees
Milestone

Comments

@MadsTorgersen
Copy link
Contributor

Directly invoked anonymous functions

Summary

Allow (parenthesized) lambda expressions and anonymous methods to be directly invoked with an argument list:

var s = o switch
{
    Person p => (name => { WriteLine(name); return name.Trim(); })(p.GetName()),
    ...
};

Motivation

Lambda expressions (together with anonymous methods) are also called "anonymous functions". We are doing more and more to make them more similar to named local function declarations: Allowing the static modifier, attributes and explicit return types. One thing we do not yet allow is invoking them directly. For that they still need to be converted to a delegate type.

There are two main motivations to allow this:

  1. As a way to enable statements and scopes in an expression context
  2. To enhance the code environment; notably to make it async

Detailed design

From a grammar perspective direct invocation is already allowed, as long as the lambda expression is parenthesized:

invocation_expression
    : primary_expression '(' argument_list? ')'
    ;

There are currently two kinds of invocation expressions: Method invocations (including extension method invocations) and delegate invocations. This proposal adds "anonymous function invocations" as follows:

For an anonymous function invocation the primary_expression shall be classified as an anonymous function. If the anonymous function is a lambda_expression with an implicit_anonymous_function_signature then the argument_list shall have a corresponding number of arguments and all of these arguments shall have types. These types are then taken to be the types of the corresponding parameters.

Considering the anonymous function to be a function member, it shall be applicable to the argument list.

At runtime an anonymous function invocation is processed as a function member invocation.

A note on parentheses

As mentioned, anonymous function invocations can only parse if the anonymous function expression is parenthesized. While the spec doesn't seem to be entirely clear about this, parenthesized expressions can already be invoked today, and the kind of invocation hinges on the classification of the expression inside the parentheses. Essentially, except for parsing, parentheses are ignored.

This holds even when the parenthesized expression doesn't have a value; e.g. if it is a method group:

void M(int i) => WriteLine(i);

M(1);     // Normal method invocation
(M)(2);   // Disallowed only because of parsing ambiguity with cast expression
((M))(3); // Allowed; parentheses are ignored

We should probably consider making the spec clearer about this, but invocation of parenthesized expressions is nothing new, and is not part of this proposal.

A note on implementation

A directly invoked anonymous function is very much like a local function. We should consider some of the same implementation strategies, e.g. passing as an extra ref argument a struct representing the closure.

Drawbacks

Direct invocation of lambda expressions, even though common in other languages, does not have great readability.

Alternatives

Natural delegate types

One alternative is for this to fall out of the "natural types for lambda expressions" feature. Essentially when a lambda expression has a natural delegate type, we would create the delegate and immediately invoke it.

The downsides are that it would not apply to lambdas without explicit parameter types, and that the semantics require a delegate to be created only to be immediately discarded (though this can likely be optimized away).

Expression blocks

The first scenario - fitting statements into an expression context - could be addressed by a dedicated feature, such as expression blocks. This would require new syntax, and has been the subject of some controversy. It would not address the second scenario; the need to create an async context for code.

Unresolved questions

No known unknowns.

Design meetings

Direct invocation of lambda expressions was discussed in C# LDM on May 10, 2021.
https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-09-28.md#ungrouped

@MadsTorgersen MadsTorgersen added this to the Likely Never milestone Dec 12, 2024
@MadsTorgersen MadsTorgersen self-assigned this Dec 12, 2024
@dotnet dotnet locked and limited conversation to collaborators Dec 12, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

1 participant