-
Notifications
You must be signed in to change notification settings - Fork 1k
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 #4748
Comments
Would it be safe to assume that although these expressions use lambda syntax that the compiler can be expected to optimize away the lambda/delegate into something more akin to a local function if not as inline code? |
How is hte signature for the lambda determined here. Given:
it looks like teh lambda is in a location where its type cannot be inferred. Or would we expand the inference locations to allow an 'invocation expression' to be a place where informatino could flow into it? So in this case, we'd see |
Both rust and Scala allow expressions to be marked as async. I think this could be done fairly straightforwardly in C#. Ideally the expression would be target typed so that you could use ValueTask instead of Task.
I would strongly urge the team to prefer expression blocks. I think going with direct lambda invocation risks the same problem as anonymous delegates which were introduced in C#2, weren't brief enough, and were effectively obsoleted by lambdas in C# 3. Whilst directly invoked anonymous functions would scratch the expression blocks itch, it has so much boiler plate that it's unlikely to be acceptable to most users who want it, and could well be replaced by expression blocks a few versions down the line. |
I agree with Yair on this. |
I agree. There may be use cases for IIFE, but I think many scenarios (especially the switch expression scenario presented here) would be much better served with expression blocks or other targeted proposals. Having to use an inline lambda invocation in that case is just really ugly. Having to separately declare arguments (inferred or not) and then pass values to them twists the flow of the code inside out. I imagine if this proposal were adopted you'd be more likely to see the following: var s = o switch
{
Person p => (() => {
WriteLine(p.GetName());
return p.GetName().Trim();
})(),
...
}; or: var s = o switch
{
Person p => (() => {
var name = p.GetName();
WriteLine(name);
return nameTrim();
})(),
...
}; Where all of those extra parens just seem really unnecessary. |
It was opting out of being async that I was expecting to see listed under the motivations. async Task FooAsync()
{
var bar = await BarAsync();
(() => {
var refLike = new SomeRefLikeType(bar);
DoSomething(refLike);
})();
await BazAsync();
} But the ability to use ref-like variables directly inside the async method when they don't cross an await is something I'd rather have anyway. |
I've got to say, this syntax on this is terrible:
This feels overly hacky, trying to synthesize multiple statements in places where only an expression is allowed. Expression blocks would go a long way to fixing this. If it's tricky using braces to mark the blocks then how about something like this:
Here's those no need for a |
When I saw switch expression come up in C# 8, I was disappointed that the branch had to be a single expression (and how hard it is to compose stuff in a single expression in C#), while a scoped anonymous block would have felt natural (being also familiar with F# and not liking python being restricted in what a lambda expression allows). Wouldn't this concern be addressable by allowing the branch part to have a scoped anonymous block in the first place? what would be the issue or what were the reasons against it? is it because of "early return ambiguity"? what about making last expression the returned one, or worst case, finding a suitable keyword? var s = o switch
{
Person p => { var name = p.GetName(); WriteLine(name); return name.Trim(); }
// ...
}; I'm not against adhoc invocability of a lambda expression, but don't feel the reason I quote @MadsTorgersen for it is good. I'd rather have the switch expression / switch statement dichotomy subside a bit or totally, because it is frustrating dance when you are used to languages that handle both with same construct, and doesn't force turning the whole code upside down (imagine risk of introducing bugs doing this on a large switch...). That dichotomy of switch expression doesn't feel natural at all and maybe C# language design team can find a way to solve it without this feature (which, again, is also fine). |
I too am more align of expression block than this feature. But actually I prefer this concept than expression block, just don't like the syntax. Maybe this is worth consider alternative syntax How about this var s = p.GetName() into (name) => {
WriteLine(name);
return name.Trim();
}; Using It actually equivalence to using at switch var s = o switch
{
Person p =>p.GetName() into (name) => {
WriteLine(name);
return name.Trim();
}
}; Can be chained var (s,i) = p.GetName() into (name) => {
WriteLine(name);
return name.Trim();
} into (name) => {
var salary = p.GetSalary();
var nameParts = name.Split(" ");
if(nameParts.Length == 2)
return ("Common Name",name,salary);
if(nameParts.Length == 3)
return ("Has Middle Name",name,salary);
if(nameParts.Length == 1)
return ("Single Name",name,salary);
throw new Exception("Unsupported name format");
};
var (s,i) = (p.GetName(),p.GetSalary()) into ((name,salary)) => {
WriteLine(name + " salary : " + salary);
return (name.Trim(),salary);
}; Also can it be async too? var s = await p.GetName() into async(name) => {
WriteLine(name);
return await GetStringData(name);
}; |
@Thaina I do like how that reads better, but I'd prefer a pipe operator Tbh, I think block expressions would be better. They could even work for async by using the async keyword: var s = await async {
string name = p.GetName();
WriteLine(name);
return await GetStringData(name);
}; Although, I'm not too fond of that, as it will have the same problem of |
For parameterless maybe But if we consider people using reactivex I think they would more align to have |
Maybe |
We can combin syntax block and direct invocation, to define lanbda with arguments instead of params.. say:
Still thinking about paramterless lambda invokayion. |
For the given sample:
|
Why define a lambda at all? IIFE is a hack in Javascript to work around that language's lack of proper scoping and accessibility. Those problems don't exist in C# which eliminates the need for such a hack. Having to define arguments to flow what should otherwise just be local identifiers seems like pointless busy work with a high potential for introducing bugs and a poor way to address expression blocks. |
For one reason, I hope that lambdas allow yield return like vb does. Vb xml literals is smart in consuming the IEnumerable, so inline iterator lambdas in vb works well. I like the idea of code blocks but can it yirld return? |
@VBAndCs #3086 should be what you're looking for. Expression blocks should completely supersede directly invoked lambdas. From LDM, it seems to be on the roadmap for C# 11, and it should be able to support flow control, including |
@timcassell |
If you read further down into the issue thread, lots of people expressed concerns over the OP's suggested syntax. The more popular syntax is akin to |
int a=3, b= 5;
var result = (x: a, y: b) => x + y; This would be easier written as:
|
Directly invoked anonymous functions
Summary
Allow (parenthesized) lambda expressions and anonymous methods to be directly invoked with an argument list:
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:
Detailed design
From a grammar perspective direct invocation is already allowed, as long as the lambda expression is parenthesized:
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 alambda_expression
with animplicit_anonymous_function_signature
then theargument_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:
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
The text was updated successfully, but these errors were encountered: