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

Feature request: Implicitly typed lambdas #14

Closed
nvivo opened this issue Jan 16, 2015 · 40 comments
Closed

Feature request: Implicitly typed lambdas #14

nvivo opened this issue Jan 16, 2015 · 40 comments

Comments

@nvivo
Copy link

nvivo commented Jan 16, 2015

Currently, it is not possible to do this:

var x = () => 1;

Instead, you need to declare the type before:

Func<int> x = () => 1;

C# should just assume these kind of lambdas are either Action or Func and infer the type.

Usually these lambdas are used as helpers for the method implementation, so it really wouldn't matter if a (string s) => true is a Func<string, bool> instead of a Predicate<string> as long as the signature matches.

@kasajian
Copy link

How do I "up" vote this?

@theoy theoy added this to the Unknown milestone Jan 16, 2015
@HaloFour
Copy link

I think that with this it would be important for C# to support a form of duck-typing delegates based on their signature, otherwise the following wouldn't work since C# would infer that predicate is a Func<int, bool>:

var list = Enumerable.Range(0, 10).ToList();
var predicate = x => x % 2;
var evens = list.FindAll(predicate);

@mattwar
Copy link
Contributor

mattwar commented Jan 16, 2015

You can say

new Predicate<int>(predicate)

but it would be nicer if C# allows this as an implicit conversion.

@mattwar mattwar closed this as completed Jan 16, 2015
@mattwar mattwar reopened this Jan 16, 2015
@MadsTorgersen
Copy link
Contributor

This is one that we talk about often. VB has implicit types for delegates (not Func and Action but compiler generated ones), and quite liberal implicit conversions between delegate types.

We could do the same in C#, and maybe we should. One concern would be that the relative cost of these conversions might be on the highside, and invisible to users because of the implicit conversions.

@agocke
Copy link
Member

agocke commented Jan 16, 2015

@MadsTorgersen Analyzers could potentially reduce the risk of that.

Otherwise, we should consider when/if it's possible to infer the type of the lambda based on its use. It would definitely not be possible to infer the type in certain situations and very expensive (in compilation time and implementation effort) in others, like use outside of the current method body. However, it may be worth simply disallowing these "var" instances for the use in the 99% case and no risk of implicit conversion pollution.

@nvivo
Copy link
Author

nvivo commented Jan 16, 2015

The idea here is just to make the code more readable. For example:

Func<Board, int, int, IEnumerable<Board>> newBoard = (rows, cols, previousGames) => { 
   // calculate a game board
   return new Board(data);
};

It would be better as:

var newBoard = (int rows, int cols, IEnumerable<Board> previousGames) => { 
   // calculate a game board
   return new Board(data);
};

It is a small difference, but I think that the types together with parameters are much more readable.

My initial idea was not to infer every parameter, but just make the declaration simpler. This syntax is enough and don't need implicit conversions.

@agocke
Copy link
Member

agocke commented Jan 16, 2015

Reminder to everyone; this is what the spec actually has to say about lambda ("Anonymous function") expressions:

An anonymous function is an expression that represents an “in-line” method definition. An anonymous function does not have a value or type in and of itself, but is convertible to a compatible delegate or expression tree type. The evaluation of an anonymous function conversion depends on the target type of the conversion: If it is a delegate type, the conversion evaluates to a delegate value referencing the method which the anonymous function defines. If it is an expression tree type, the conversion evaluates to an expression tree which represents the structure of the method as an object structure.

For the purposes of this discussion, if you think that a lambda is of type Func or Action I would really suggest you go read all of section 7.15 of the C# specification, since we're probably going to end up pretty far in the weeds here.

@DustinCampbell
Copy link
Member

Not a perfect solution, but remember that you can create aliases for closed generic type names when they get long:

using MyFunc = System.Func<Board, int, int, System.Generic.Collections.IEnumerable<Board>>; 

The later:

MyFunc newBoard = (int rows, int cols, IEnumerable<Board> previousGames) => { 
   // calculate a game board
   return new Board(data);
};

@nvivo
Copy link
Author

nvivo commented Jan 17, 2015

@agocke,

I probably didn't express myself well. The point is not forcing the lambda to be Action or Func, but be able to use var <identifier> = <lamba expression>;. @DustinCampbell idea doesn't solve this. Looking at the language:

var a = true;
var b = SomeMethod();
var c = (string x) => x.Length;
var d = () => 1;

Looking at this code, there is no reason why c and d should be written differently from a and b. The compiler is able to correctly infer the delegate type. I mentioned Action or Func because I know the compiler already uses those delegates when building Linq expressions from lambdas.

It wouldn't matter if the compiler internally created some anonymous delegate signatures.

From what you said, it seems that the problem is that the spec states that the way lambdas are interpreted depends on the destination of the value. It may be a delegate or an expression tree.

But in this specific case, couldn't it include something like "when a lambda expression is assigned to an implicitly typed variable, it is always evaluated as a delegate"?

Remember this is a method-scoped variable primarily used for closures. If you need to do anything else, the previous syntax still works and you can force an expression tree with:

Expression<Func<int>> x = () => 1;.

@mikedn
Copy link

mikedn commented Jan 17, 2015

I think that for this to work really well the runtime would have to be convinced to allow casting between different delegate types that have identical signatures. Then the whole problem of implicit conversions would go away because their cost will be 0.

When it comes to invoking a delegate it makes no difference if the delegate type is Predicate<string> or Func<string, bool>. The method signature is identical and the generated code will also be identical.

@nvivo
Copy link
Author

nvivo commented Jan 17, 2015

@mikedn, I'm not getting the reason to care about casting or changing the runtime. It is all compilation time checking I'm proposing.

The way "var" works is that the compiler uses whatever it sees on the right hand side of the expression. Once the type is set, everything remains the same.

When using var, you don't care about the type of "var". If you need a delegate to be of a specific type, just create that with that type. I think this solves most of the problems without creating edge cases.

You can already do this pretty easily:

var p = new Predicate<string>(s => s.Length > 10);

The reason I think it is better to not care about casting is because this:

var predicate = (string s) => s.Length > 10;
var filtered = list.FindAll(predicate);

to me makes as much sense as this:

var ticks = "635556672000000000";
var date = new DateTime(ticks);

There shouldn't be any conversion or casting. If you need a long, create a long. If you need a predicate, create a predicate.

@mikedn
Copy link

mikedn commented Jan 17, 2015

Well, I don't get why you'd think that the predicate/FindAll example doesn't make sense. Perhaps that's because you're used to the way things work now. Let's take a simpler example which doesn't involve generics because they can complicate things:

delegate void foo();
delegate void bar();

Is there any difference between foo and bar except the name? Nope, no difference whatsoever.

Let's try to use these 2 delegates:

static void fn() {
}

static void Main() {
    foo f = fn; f(); // works, calls fn
    bar b = fn; b(); // works, calls fn
    f = b; // doesn't work
}

Both delegate types do exactly the same thing, you can delete one of them and replace all its usages with the other, this program will work exactly like before except that f = b will be now valid. There's only one case where such a substitution wouldn't work: overloading. But I don't remember ever seeing method overloading on delegate parameter and I hope I won't ever see such a bad use of overloading.

This weird limitation doesn't exist in C/C++. Function types simply don't have associated names, void() is a void function type with no parameters and there's no other type with the same meaning. Names may be associated to such types via typedef but those are simply aliases, not distinct types. As a result, implementing a similar feature in C++ is a no-brainer: auto f = fn;. The type of f is deduced as pointer to void() and f can be used anywhere where a pointer to a void function is expected.

@axel-habermaier
Copy link
Contributor

Another idea: What you actually want are local function declarations. So, why not:

int MyFunc()
{
      int innerFunc(int x) => x * 2;
      return innerFunc(4);
}

That's pretty close to how it works in F#. Consider:

let MyFunc () =
   let innerFunc x = x * 2
   innerFunc 4

No one would write let innerFunc = fun x -> x * 2, which would be equivalent to var innerFunc = (int x) => x * 2

@nvivo
Copy link
Author

nvivo commented Jan 17, 2015

@mikedn,

I see what you mean now. That makes sense. But then I think these are two independent ideas.

  1. Be able to compile var <identifier> = <lambda expression> requires nothing new, it just requires the compiler to see this as <some deletage type> <identifier> = <lambda expression>.
  2. Be able to cast delegates with the same signatures, or "intern" delegate signatures, so all signatures point to the same reference regardless of how they are called require some changes to the runtime.

To me specifically, I keep bumping into the missing "var id = lambda" syntax much more often. But that is probably just my use case.

@HaloFour
Copy link

@mikedn,

The weird limitation doesn't exist in C/C++ because in those two languages a function pointer is just that, a pointer.

In .NET all delegates are separate sealed classes which happen to inherit from MulticastDelegate and provide a specialized Invoke method, which the runtime is designed to regard as a form of function pointer but with the additional functionality of multicasting and optional this binding. The runtime does not allow casting from one delegate type to the other because they are separate unrelated classes.

I agree, however, that it would be very nice if C# glossed over this detail and made it appear that you could simply assign a variable of one delegate type to a variable of a different delegate type as long as the signatures matched. The problem is that the only way to do this in .NET is to actually bind the new delegate to the Invoke method of the existing delegate instance, e.g.:

Func<int, bool> f1 = i => true;
Predicate<int> f2 = new Predicate2<int>(f1.Invoke); // the same as new Predicate<int>(f1)

Invoking the new delegate results in two delegate dispatches each of which has more overhead than a normal method call. Granted, we're talking about fairly minuscule amounts of time but if that delegate is called incredibly frequently, such as in a LINQ query over a large number of elements, it could add up. This is the issue that would be nice to solve in the runtime in order to make similar delegates directly assignable to one another.

Note that VB.NET has allowed this relaxed conversion of delegates for some time now by hiding the detail of creating a new delegate and incurring that overhead.

@mikedn
Copy link

mikedn commented Jan 17, 2015

Erm, I have no idea what "a function pointer is just that, a pointer" is supposed to explain. Isn't a managed reference just that, a reference? The reason why this weird limitation doesn't exist in C/C++ has nothing to do with pointers and I specifically avoided talking about pointers. The reason is that C/C++ simply doesn't allow you to create such equivalent but distinct types in the first place.

And I know very well what a delegate is and why the runtime doesn't allow the cast. The point is that it could allow the cast and that would avoid the problem mentioned by Mads Torgersen earlier, the potential cost of the improvised conversions the C# compiler would need to make to get around the runtime limitation.

@HaloFour
Copy link

You didn't mention pointers, but what's what "function types" are and it's really the closest thing you'll find to a delegate built-into the C/C++ languages, unless you break out functors or other function-like objects, which are mostly syntax candy via operator overloading. I'm not sure what you mean about C/C++ not allowing the creating of equivalent but distinct types. Assuming you used functors you could definitely define two which are equivalent and you can force the compiler to cast between them but the results at runtime will probably not be pretty.

The remaining explanation was for the benefit of anyone reading the thread and I apologize if I appeared to be lecturing you.

@mattwar
Copy link
Contributor

mattwar commented Jan 17, 2015

A delegate is an actual class instance that holds onto the function pointer and the instance pointer. When a lambda is converted to a delegate, a delegate instance is allocated. To convert to another delegate type you have to construct an instance of the new delegate type. With C++, you just have to cast the function pointer, no allocations involved. This is the cost that Mads was alluding to. Typically, casts in C# do not cause allocations either (except possibly with user-defined conversions and boxing), and object identity is preserved.

@mikedn
Copy link

mikedn commented Jan 17, 2015

Function types and pointers aren't the same thing. The void() that I used in my example is a type in it's own right and it's certainly not a pointer. From it you can build a pointer (void(*)()), a reference (void(&)()) or a wrapper object like std::function<void()>.

@MadsTorgersen
Copy link
Contributor

The more I think about it, the more I think that @axel-habermaier has the right idea that these scenarios are better addressed by adding local functions to the language; i.e., method declarations inside of method bodies.

To use @HaloFour's predicate example, it would look like this:

var list = Enumerable.Range(0, 10).ToList();
bool predicate(x) => x % 2; // local function
var evens = list.FindAll(predicate);

That way the delegate isn't even created until the method group predicate is passed as an argument and thereby implicitly converted to a delegate of the expected type.

One downside is that lambdas have implicit (inferred) return type, whereas named methods today are always required to specify an explicit return type (bool above). But we could lift that restriction for (at least) local functions, allowing var instead:

var list = Enumerable.Range(0, 10).ToList();
var predicate(x) => x % 2; // inferred return type bool
var evens = list.FindAll(predicate);

I think this would address many of the scenarios where a need for implicitly typed lambdas is currently felt, as well as being more generally useful.

Thoughts?

@mikedn
Copy link

mikedn commented Jan 19, 2015

That should work pretty well and I've seen requests for local functions before. They have the advantage that they can be called from the enclosing function without having to pay the cost of delegate creation.

I think the main concern with this solution is delegate caching. Should the compiler attempt to cache the delegate in cases like the following?

foreach (var list in listOfLists) {
    var e = list.FindAll(predicate);
    ...
}

If it doesn't cache then you risk having people coming back and saying: hey, it would be nice to be able to write var d = predicate; 😄

@paulomorgado
Copy link

Other than the allocation and invocation of the delegate, what are the other benefits of local functions over lambdas or anonymous delegates?

@axel-habermaier
Copy link
Contributor

@MadsTorgersen: Inferred return types for local function declarations would certainly be interesting. For "real" methods, however, I would not support adding that feature (or else you probably should also add var for fields and (potentially?) properties). But then again, I'm also not a fan of C++'s auto return types.

Optimization is certainly an interesting topic when considering local function declarations, as there are many opportunities. Let's go through a couple of cases:

int F()
{
   int f(int x) => x;
   return f(3);
}

In that case, the compiler could simply emit a static function into the class which is then called directly. No delegate construction is required. As efficient as it gets.

static int y;
int F()
{
   int f(int x) => x + y;
   return f(3);
}

Same as above, as the local function only accesses its own parameters and static variables of the enclosing class. The closure does not require a display class in that case.

int y;
int F()
{
   int f(int x) => x + y;
   return f(3);
}

We can simply create an instance method in that case to capture the closure semantics of the instance variable. Again, no delegate needs to be constructed.

int F(int y)
{
   int f(int x) => x + y;
   return f(3);
}

This is an interesting case. We either have to instantiate a display class that captures the method parameter. Or we change the signature of f such that y is also passed as an parameter (but that is most likely not always possible, I'd guess). In any case, no delegate construction is required. The invocation of f simply invokes the generated method on the display class.

Generally, for small functions like the one above, it might even be beneficial to completely inline them. So the compiler should probably emit them with the aggressiveinlining flag, allowing the JIT compiler to do its inlining magic.

When a local function is passed to another function, a delegate must of course be created. In that case, I suggest to use the same rules for caching as for lambdas (i.e., only cache functions without a closure, if I recall correctly).

Two other things to consider: Do we want to expose local functions in the metadata explicitly? That is, should the reflection APIs be extended such that MethodInfo has a property IEnumerable<MethodInfo> LocalMethods { get; }?

Local function declarations probably should support attributes, so that I'm able to write, for instance:

int F(int y)
{
   [DebuggerHidden]
   int f(int x) => x + y;
   return f(3);
}

@axel-habermaier
Copy link
Contributor

@paulomorgado: Conceptual clarity.

@paulomorgado
Copy link

@axel-habermaier,

Would you care to elaborate on that conceptual clarity?

What is more conceptual clear in this:

void M()
{
    int m() => 0;
    ...
    var i = m();
}

than here:

void M()
{
    Func<int> m() => 0;
    ...
    var i = m();
}

?

Don't get me wrong, I'm ALL for conceptual clarity.

@sharwell
Copy link
Member

@MadsTorgersen I looked over some code that uses lambdas and realized there are two cases where local function definitions would be particularly interesting.

  1. Writing recursive "lambda" functions.
  2. Assuming local function definitions cannot access local variables of the method in which they are defined, better (explicit) control over closure variables.

@mikedn
Copy link

mikedn commented Jan 19, 2015

Let's be realistic, this has nothing to do with clarity. It's just an attempt at working around delegate issues: allocation costs (and in general, absence of any sort of optimizations) and impossibility of converting between delegates with the same signature.

@axel-habermaier
Copy link
Contributor

@paulomorgado (also @mikedn): Your first example defines a local function m which you subsequently call. Your second example defines an anonymous local function and a delegate m to that function which you subsequently call. Sure, the syntactic difference is small, the semantic difference (*) is rather large, I'd say. At least, the first example was a lot easier to explain, requiring a lot less knowledge about the internals of .NET and the C# compiler. So the first example directly states what you want to achieve, whereas the second example first constructs some unnamed function which it subsequently assigns a name to, so that it can be indirectly called later. See my point?

Also, look at my F# example again, which highlights the conceptual problem at a syntactical level already:

let M () =
   let m1 () = 0 // Simple local function
   let m2 = fun () -> 0 // The delegate way

(*) Semantic in the sense of programming language semantics; the actual output is, of course, the same, disregarding the different heap layout.

@paulomorgado
Copy link

@sharwell,

  1. You "can" write recursive anonymous methods using lambdas:

    Func<int, int> f = null;
    f = (i) => i == 0 || i == 1 ? 1 : i * f(i - 1);

    var x = f(5)

  2. Do you really believe that it will be accepted that local functions won't be able to close over function parameters and function local variables? Won't that cause confusion for the average developer?

@axel-habermaier,

Other than delegate allocations, what's the practical difference? And I'm not even assuming that most of the local functions/anonymous methods are used to close over locals and to pass along as a delegate.

I think a lot of assumptions are being made about the implementation. I'm assuming that the difference between a local function and an anonymous method is the lack of the need for a delegate. I assume it will implemented just the same way.

Maybe it was a bad decision the name the generated methods based on the type name instead of the function name.

@axel-habermaier
Copy link
Contributor

@paulomorgado: Other than performance, probably none if you don't care about the (admittedly only slightly) simpler semantics. It's just an alternative I mentioned so that we can discuss the potential advantages or disadvantages. It probably all comes down to personal preference. For me, especially coming from F#, local function declarations just make more sense than "named anonymous delegates". It seems like @MadsTorgersen and @sharwell found the idea interesting, so there might be something to it.

While I seem to be unable to come up with better reasons other than the somewhat obscure "conceptual clarity" argument and delegate avoidance (which seems to be a goal we can all agree on), @sharwell mentioned recursion as a potential benefit (and rightly so, because the way recursive lambdas work at the moment is just horrible and completely non-intuitive).

Do you have any arguments specifically against local function declarations?

@nvivo
Copy link
Author

nvivo commented Jan 19, 2015

@axel-habermaier

I think the syntax of lambdas as much cleaner than local functions. I know it is the same syntax used for real functions but looking at some code looks like there is a syntax error somewhere.

int X() {
int Y() { // is this supposed to be a inner function or I forgot to close braces?

Looks like while adding or removing a brace, the compiler could treat the entire class as a function body and start pushing reference errors everywhere in the project, instead of having a clear indication of a missing brace somewhere.

Another argument is that to me, looking at an inner function as a "value" rather than a "language construct" seems like a nicer abstraction. It is also a syntax that is already being used, so the learning curve is lower. As I said before, it is just changing Func<int> x = () => 1 to var x = () => 1.

That doesn't mean I think it needs to be delegates.The compiler already compiles lambdas in different ways today. That could be just another case. Private static methods sounds like a good solution.

In any case, I'd like to say that accessing local method variables is very important, so if local functions means losing that, it wouldn't make any sense.

@svick
Copy link
Contributor

svick commented Jan 19, 2015

That doesn't mean I think it needs to be delegates.The compiler already compiles lambdas in different ways today. That could be just another case. Private static methods sounds like a good solution.

That doesn't sound like a good idea to me, because it would mean x would behave very differently than other local variables, for example, you wouldn't be able to write x.GetType() or object o = x;.

@nvivo
Copy link
Author

nvivo commented Jan 19, 2015

@svick,

That doesn't sound like a good idea to me, because it would mean x would behave very differently than other local variables, for example, you wouldn't be able to write x.GetType() or object o = x;.

Why not? You can assign a method currently to another variable:

private void SomeMethod() { }
Action x = SomeMethod;

which is compiled to:

Action x = new Action(SomeMethod);

Once x is a delegate, both your examples should work.

var x = (string s) => s.Length;  

// would compile to
private static SomeAnonymousMethod(string s)
{
    return s.Length;
}
...
Func<string, int> x = new Func<string, int>(SomeAnonymousMethod); 

var y = x; // works fine by type inference
object z = x; // works fine because Delegate extends Object
x.GetType(); // works fine because x is an actual type

The only thing with this is that a lambda could not be turned into an expression tree. But I think local function declarations wouldn't either. And if they could, the same solution could be applied to lambdas.

But I'm probably missing something here as @MadsTorgersen leans toward local function declarations.

@svick
Copy link
Contributor

svick commented Jan 19, 2015

@nvivo I was quoting the part of your post where you said it doesn't need to be a delegate, so I don't understand how does answering that it's a delegate make sense.

Maybe I misunderstood what you meant? Or do you mean that the variable won't be a delegate, unless it has to? That would be weird.

@nvivo
Copy link
Author

nvivo commented Jan 20, 2015

@svick, there are a lot of proposals here, so it is getting confusing. =)

My first idea was to compile var x = () => 1 as Func<int> x = () => 1. That is what we were referring as "compile a lambda to a delegate". The C# spec currently says that a lambda has no type, so that would break the spec. Also, in this solution, the delegate used is incompatible with other delegates with the same signature (eg. Func vs Predicate)

@axel-habermaier suggested that instead of that, a lambda could be compiled to a static method sometimes. This is what I referred as compiling to a static method. In this case, the method is compatible with any delegate that matches the signature. And if you are just creating a simple local function that is just called, but there is no reflection or anything fancy going on, the compiler could optimize that to a static method call, which would avoid the delegate at all (and probably match 99% of use cases).

My example just showed that even if it is compiled to a static method, the compiler can generate code that allows your examples to work by using delegates. It doesn't need to be all or nothing.

Why do you think it would be weird to do that? Sounds like normal compiler optimization to me.

@paulomorgado
Copy link

@axel-habermaier, like I said before, "Don't get me wrong, I'm ALL for conceptual clarity."

For those use cases that a delegate is not needed, it makes a lot more sense to have local functions. Recursion is also a valid use case - even if a delegate is needed.

So it comes down to:

  1. Conceptual clarity
  2. Ocasional delegate allocation and invocation.

That's enough for me.

@sharwell, how strongly do you feel that closures should not be possible for local functions?

@agocke
Copy link
Member

agocke commented Jan 20, 2015

@MadsTorgersen This still means we would need to infer the type of the local function based on later usage, correct? Or are we still considering VB-style delegate relaxation?

@ashmind
Copy link
Contributor

ashmind commented Mar 1, 2015

There seem to be quite a few different proposals here at the same time.
The case I am most interested in is var x = (Y y) => y;, whether through Action/Func or static method -- inner functions seem like a whole new complexity to replace minor inconsistency.

As for the conversion, item 4 in #420 should cover it.

@gafter gafter removed this from the Unknown milestone Apr 20, 2015
@weitzhandler
Copy link
Contributor

@kasajian #14 (comment):
How do I "up" vote this?

This is actually a compelling feature.

@MadsTorgersen
Copy link
Contributor

Per #3912 and #3911 we will probably never do this. Instead, local functions address most of the scenarios.

AdamSpeight2008 referenced this issue in AdamSpeight2008/roslyn-AdamSpeight2008 Feb 17, 2017
CyrusNajmabadi added a commit that referenced this issue May 25, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
Add tests for underlining reassignments (second edition)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests