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] Non-Capturing Lambdas with ==> #11620

Closed
RobJellinghaus opened this issue May 27, 2016 · 16 comments
Closed

[Proposal] Non-Capturing Lambdas with ==> #11620

RobJellinghaus opened this issue May 27, 2016 · 16 comments

Comments

@RobJellinghaus
Copy link

RobJellinghaus commented May 27, 2016

A well-known issue in C# programming is inadvertent lambda capture, in which a lambda, which was intended to be statically allocated, mistakenly closes over some local state -- which forces a full display class allocation and delegate allocation. ReSharper evidently has a warning for this case, but it is as often intentional as not.

Some way for the programmer to express their intent -- that a particular lambda not close over local state -- is needed.

#117 is a proposal to bring full-fledged C++-like lambda capture lists to C#. This would solve the problem, but at the cost of extra syntax for all lambdas, and a maintenance burden for lambdas which do capture (proportional to the number of locals captured).

The main distinction that is needed is binary: is this lambda intended to be capturing, or not?

This proposal is to introduce a new token to the language when defining lambdas: the ==> token. The ==> token, when used in a lambda definition in place of =>, indicates "this lambda must not capture local state," and causes a compiler error if there are any open variables in the lambda.

So in the case of

intEnumerable.Where(i ==> i > 0).Select(i ==> i.ToString())

both of the lambdas would be fine, as neither one captures local state.

But this would be an error:

intEnumerable.Where(i ==> i > intEnumerable.Count)

as the lambda is defined with ==> but references a local variable.

(The motivation for "==>" as the token is that it is obviously close to the existing syntax, in fact so close as to be almost completely unobtrusive (and hence widely usable), while hopefully fitting reasonably well into the existing tokenization rules of the language.)

As far as expressing this in the IL, it is possible that this syntax could result in applying an attribute to the generated method for the lambda, as suggested under item 6 of #1898

The implementation intent is that the compiler reliably lifts ==> lambdas into static allocations, with no runtime allocation cost required when invoking them.

@RobJellinghaus RobJellinghaus changed the title Non-Capturing Lambdas with ==> [Proposal] Non-Capturing Lambdas with ==> May 27, 2016
@HaloFour
Copy link

While it would involve more syntax I kind of think that such a problem could easily be handled by analyzers. An assembly or class could be annotated with attributes to specify whether capturing lambdas are allowed, and then additional attributes could be applied to specific methods to white/blacklist capturing:

[assembly:  AllowCaptures(false)]

static class Program {
    static void Foo() {
        int max = 5;
        int[] array = Enumerable.Range(0, 10).Where(i => i < max).ToArray();  // analyzer error
    }

    [AllowCaptures(true)]
    static void Bar() {
        int max = 5;
        int[] array = Enumerable.Range(0, 10).Where(i => i < max).ToArray();  // good
    }
}

It's more verbose and the granularity could only be at the method level, but it does achieve the desired goal.

@RobJellinghaus
Copy link
Author

I am not sure method granularity is adequate. Though I agree this would be possible to prototype without parser changes, which is a significant benefit.

I also agree that in many cases, one does want to ensure that all lambdas in a given code region don't capture, so perhaps it would be a good experiment to try -- especially accompanied by reliable static lifting of non-capturing lambdas.

@GSPP
Copy link

GSPP commented May 29, 2016

Very few code bases are so allocation sensitive that this warning would be of any use. This feature is too niche for the C# language.

Wasn't there a proposal to allow putting compiletime-only attributes anywhere? This could be implemented through static analysis looking for such attributes.

@AlgorithmsAreCool
Copy link

I think i agree that this is too niche. If your hot path is so allocation sensitive then i think it is reasonable to fallback to more basic language constructs that involve less compiler magic.

@aluanhaddad
Copy link

I don't see the benefit offered by this. The value will be captured if it is used. If it is used, it is needed. In other words you very likely need to write a different function with different semantics. I also dislike the idea of introducing a new syntax for anonymous methods, and less powerful anonymous methods at that.

@iam3yal
Copy link

iam3yal commented Jun 1, 2016

I really like how lambdas work in C++ and really want it to work like this.

In C++ I have full control over what gets captured and what doesn't and how I want it either by value or by reference, I'm biased but I'm sure that new comers to the C++ syntax (or capture list) will have no problem to pick it up simply because it's explicit and very clear, not to mention more powerful.

Adding extra character doesn't really tell me anything about what it does but annoys me, it's ugly, at least in my opinion.

@AlgorithmsAreCool
Copy link

@eyalsk See #117

@iam3yal
Copy link

iam3yal commented Jun 1, 2016

@AlgorithmsAreCool yeah, I know about this, thanks! :)

Just thought to share my opinion about this suggestion.

@dmartensson
Copy link

A workaround that can be used to avoid capturing is to define and return the lambda from at static method and pass all variables to the method.

Then only the passed variables will be captured.

Example (a bit contrived but I have used a similar version to solve over capture)

void Main()
{
	var a = new[] {1,3,5,7,9,11,13,15,17};
	
	var l = a.Select(x => Test(x));
	
	l.Select(x => x.Invoke()).Dump();
}

//This method will only capture the v variable.
static Func<bool> Test(int v) => () => v < 10;

@gulshan
Copy link

gulshan commented Jan 16, 2017

I would like a thin arrow -> for non-capturing lambdas, while current => thick arrow continues to be capturing lambda. One more thing I would like have with non-capturing lambdas, type inference (with let keyword). Allowing something like- let sqr = x -> x*x;

@aluanhaddad
Copy link

I still do not see any reason to have this in the language at all. It adds syntactic complexity and noise to introduce a less expressive form of lambdas. If you do not want to close over any outer lexical bindings, do not use them in the lambda. If you use the value it is needed.

@HaloFour
Copy link

@gulshan

The -> operator already exists in C# for pointer dereferencing member access, which is why it couldn't be used as the normal lambda operator. It would create syntax ambiguities.

@alrz
Copy link
Contributor

alrz commented Jan 16, 2017

I don't understand, an empty capture list indicates a non-capturing lambda [](x, y) => {}, do we really need a separate syntax for that case?

@RobJellinghaus
Copy link
Author

If we had capture lists, then no, we would not.

The overall point of the proposal was to allow the programmer to express that the lambda should not capture, so that any inadvertent capture would be flagged as an error. The assumption is that non-capturing lambdas are an important enough case (due to their ability to be statically allocated) that special support to facilitate their use is warranted.

"Very few code bases are so allocation sensitive that this warning would be of any use. This feature is too niche for the C# language."

Perhaps so. In the code bases I personally work with (systems code, including games and browser components), performance demands and allocation-sensitivity are both very high. The C# language's reach is broadening, and the point of this feature is to make this scenario less niche.

@rjamesnw
Copy link

rjamesnw commented Apr 4, 2017

I'm pushing for non-capturing lambdas and this is why: http://stackoverflow.com/q/43217853/1236397

Since C# 6, people shouldn't be forced to push all static lambdas for expression building now into the class making clutter. I vote for either some non-capturing syntax, or the "static" modifier, which is basically much the same thing.

@jcouv
Copy link
Member

jcouv commented Oct 22, 2017

I'll go ahead and close this discussion. Feel free to continue on csharplang.
Here's one issue that's related: dotnet/csharplang#275 (there may be others too).
Thanks

@jcouv jcouv closed this as completed Oct 22, 2017
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