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: Anonymous Lambda Invocation #14401

Closed
iam3yal opened this issue Oct 10, 2016 · 23 comments
Closed

Proposal: Anonymous Lambda Invocation #14401

iam3yal opened this issue Oct 10, 2016 · 23 comments

Comments

@iam3yal
Copy link

iam3yal commented Oct 10, 2016

Disclaimer: I didn't really know how to call it so sorry if the name in the title doesn't make sense. :)

Today, in order to invoke a lambda we must assign it to a variable and finally invoke it through the variable.

Action:

Action action = () => Console.WriteLine("Hello World"); 
action();

Func:

Func<string> func = () => "Hello World";
Console.WriteLine(func());

Finally, If we really want to avoid the extra variable declaration then we can create an instance of the delegate and then invoke it but this syntax is less intuitive and readable and is used less and less as time passes.

new Action<string>(Console.WriteLine)("Hello World");

So what I'm proposing is to allow the same thing for lambda:

I know that the parenthesis operator is mostly used for casting and function calls but I wonder if it make sense to expand this to allow the examples below to work.

I mean after all this is possible: Foo(() => Console.WriteLine("Hello World"));

I'm mostly speaking about the simple cases here of Action and Func<T> as syntactic sugar but maybe it can be extended to support all the variations of these types.

Example 1:

(() => Console.WriteLine("Hello World"))

Equivalent to:

Action action = () => Console.WriteLine("Hello World"); 

Example 2:

(() => Console.WriteLine("Hello World"))();

Equivalent to:

Action action = () => Console.WriteLine("Hello World"); 
action();

Example 3:

(() => "Hello World")

Equivalent to:

Func<string> func = () => "Hello World"

Example 4:

Console.WriteLine((() => "Hello World")());

Equivalent to:

Func<string> func = () => "Hello World";
Console.WriteLine(func());

Why do we need it?

Well, sometimes introducing a variable is completely redundant, the classic example is when you use extension methods like in the FluentAssertions assertion library where it offers some extensions to Action and Func<T> in order to validate all sorts of things like whether the method throws.

(() => XmlCommandRootElementConverter.ToCommandModelCollection(null)).ShouldThrow<ArgumentNullException>();

One of the issues with a temporary variable is refactoring, when you rename the method let's say I'm calling the variable toCommandModelCollection and I'm renaming the method later then I have to rename the variable separately and while it's not a major issue it's still error-prone.

@iam3yal
Copy link
Author

iam3yal commented Oct 10, 2016

I just noticed that there's some ambiguity in my examples so question is whether the compiler can look into how it's used and emit the correct delegate? not sure whether it make sense.

@HaloFour
Copy link

Same problem as inferred lambdas, the compiler can't tell which delegate to use. The compiler has no knowledge of any particular delegate class, including Action<...> or Func<...>, nor does it treat any particular delegate class differently from any other.

@iam3yal
Copy link
Author

iam3yal commented Oct 10, 2016

@HaloFour So in its current state it's not doable, can't they embody this knowledge into the compiler? I mean I imagine they can run some analysis and know these sorts of things? isn't?

@HaloFour
Copy link

@eyalsk

Probably not, particularly in your case where you're depending on the type being known in order to then resolve extension methods. There's no reason why the compiler would select, say, Func<int, bool> over, say, Predicate<int> or any other possible delegate that happens to share the same signature.

@iam3yal
Copy link
Author

iam3yal commented Oct 10, 2016

@HaloFour I understand, thanks! :)

@DavidArno
Copy link

@eyalsk,

I'm missing something here. In what way is:

(() => XmlCommandRootElementConverter.ToCommandModelCollection(null))
    .ShouldThrow<ArgumentNullException>();

any different to:

XmlCommandRootElementConverter.ToCommandModelCollection(null)
    .ShouldThrow<ArgumentNullException>()

In other words, what is the lambda doing in your examples, as you appear to be immediately executing them?

@DavidArno
Copy link

DavidArno commented Oct 10, 2016

Today, in order to invoke a lambda we must assign it to a variable and finally invoke it through the variable.

That's not actually true. There is a trick that a number of C# functional-extensions libraries use, of defining static methods that coerce lambdas into a specific type. They can be used to invoke lambdas without assigning them to a variable first. For example, if you nuget my own Succinc<T> package, you can do the following:

using static SuccincT.Functional.TypedLambdas;
...
Action(() => Console.WriteLine("Hello World"))();

I haven't supported Func<T> lambdas, but a slightly different version of your example 4 (Using Func<string, string> would be:

Console.WriteLine(Func((string name) => $"Hello {name}")("Fred"));

I'm still unclear why you'd want to do this though. The intended purpose of these typing functions is to allow the following two lines to be equivalent:

Func<string, string> func = name => $"Hello {name}";
var func = Func((string name) => $"Hello {name}");

thus allowing pseudo implicit casting of lambdas and thus var to be used.

@alrz
Copy link
Member

alrz commented Oct 10, 2016

Related: #3990

@iam3yal
Copy link
Author

iam3yal commented Oct 10, 2016

@DavidArno

The difference between this:

(() => XmlCommandRootElementConverter.ToCommandModelCollection(null))
    .ShouldThrow<ArgumentNullException>();

And this:

XmlCommandRootElementConverter.ToCommandModelCollection(null)
    .ShouldThrow<ArgumentNullException>()

Is that ShouldThrow is an extension method of a 3rd-party library that extends only Action and the method XmlCommandRootElementConverter.ToCommandModelCollection(null) doesn't return an Action but some other type so it's not possible to call ShouldThrow on it directly.

That's not actually true. There is a trick that a number of C# functional-extensions libraries use, of defining static methods that coerce lambdas into a specific type. They can be used to invoke lambdas without assigning them to a variable first. For example, if you nuget my own Succinc package, you can do the following:

Well, technically you pass it to a function so you do assign it to a variable but this wasn't really the point, the syntax was more my point here.

And yeah I know that trick I also kinda implied it in my post when I wrote the following:

I mean after all this is possible: Foo(() => Console.WriteLine("Hello World"));

But dunno I thought/hoped like it could be a great idea to avoid all these tricks and have a natural syntax where the dirty tricks are left to the compiler. :)

p.s. I'll check the library. :)

@svick
Copy link
Contributor

svick commented Oct 10, 2016

(() => XmlCommandRootElementConverter.ToCommandModelCollection(null))
    .ShouldThrow<ArgumentNullException>();

Isn't it clearer to use the extension method as a normal method (in combination with using static)?

ShouldThrow<ArgumentNullException>(
    () => XmlCommandRootElementConverter.ToCommandModelCollection(null));

@DavidArno
Copy link

@eyalsk,

But dunno I thought/hoped like it could be a great idea to avoid all these tricks and have a natural syntax where the dirty tricks are left to the compiler. :)

I agree. and I don't see the current position of "The compiler has no knowledge of any particular delegate class, including Action<...> or Func<...>, nor does it treat any particular delegate class differently from any other." (as @HaloFour put it) as a reason not to change this. Add a Unit type and have the compiler implicitly choose Func<T1, ... Unit> for all "void" lambdas and Func<T1, ... TR> for all function lambdas. Job done.

@HaloFour
Copy link

@DavidArno

Add a Unit type and have the compiler implicitly choose Func<T1, ... Unit> for all "void" lambdas and Func<T1, ... TR> for all function lambdas. Job done.

Nope. Even assuming that were possible (which it's not, even with a Unit type Func<T, Unit> would not be compatible with Action<T> as they would produce fundamentally different IL), that still doesn't solve the problem of lack of signature equivalence between delegate types. Func<int, bool> is not compatible with Predicate<int> and there's no reason for the compiler to assume that they are. Even if the CLR could treat them as equivalent (or the dual-invocation is not considered enough of a reason to not allow equivalence) without a type there's still no target from which to attempt to resolve extension methods. Why should the compiler assume that () => true is any one specific delegate type?

Given the relatively minor inconvenience caused by much of this I don't see it justifying the massive CLR changes required to implement want you'd like to see to support said language features.

@DavidArno
Copy link

@HaloFour,

Interesting point re Predicate<T>. I don't use that type; I'd use Func<T, bool>. I'm describing implicit typing, ie for the following:

var f = (int i) => i == 0;

I see no reason why the compiler can't implicitly cast this to Func<int, bool> and if I wanted a Predicate<int>, then I'd explicitly specific its type. But maybe I ought to use the latter more and I'd see it differently.

Anyway, this topic is pretty moot: the language team have ruled out implicitly-typed lambdas, favouring local functions instead, so it's unlikely that what @eyalsk is asking for would ever happen.

@HaloFour
Copy link

@DavidArno

I see no reason why the compiler can't implicitly cast this to Func<int, bool>

The compiler can quite easily. The simplest way is to create an instance of the target delegate type which targets the Invoke method of the source delegate type. The issue with that is that invoking the target delegate has to invoke the source delegate which doubles the overhead. Every time you cast/convert you add another layer of overhead. It's not a lot of overhead; about the same as a virtual method call per invocation.

I personally like the syntax of inferred lambdas and I'd love to see them added to the language. I'm not opposed to having the compiler more bound to the Action and Func families of delegates. I'm not arguing against the feature, just pointing out the hurdles that would have to be addressed/acknowledged.

@dsaf
Copy link

dsaf commented Oct 11, 2016

I'm not opposed to having the compiler more bound to the Action and Func families of delegates.

I guess if they are coupling tuples to a specific type, might as well do the same for delegates, lists and dictionaries.

@sirgru
Copy link

sirgru commented Oct 27, 2016

Ughhh... aside from the general usefulness, or ability to be implemented, that thing looks ugly, not to mention that it's easy to miss in the flurry of parenthesis. It's ugly in js by js standards of uglyness:
https://youtu.be/eGArABpLy0k?t=1m12s

It should be C# style: (() => Console.WriteLine("Hello World")).Invoke()

@DavidArno
Copy link

@sirgru,

It should be C# style: (() => Console.WriteLine("Hello World")).Invoke()

How does adding Invoke deal with "the flurry of parenthesis"?

@sirgru
Copy link

sirgru commented Oct 27, 2016

@DavidArno Separates them visually and makes it clear it's an invocation.

@dsaf
Copy link

dsaf commented Oct 27, 2016

@sirgru I am pretty sure your style would also be possible. That's not the point here.