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

Lambdas omitting parameters #20

Closed
Khazuar opened this issue Jan 17, 2015 · 5 comments
Closed

Lambdas omitting parameters #20

Khazuar opened this issue Jan 17, 2015 · 5 comments

Comments

@Khazuar
Copy link

Khazuar commented Jan 17, 2015

I often have the situation of functions taking a lambda-expression as a parameter which take a handful of parameters themselves. Something like this:

someFunction((foo, bar, thing, another) => ..);

Of course, delegates with many parameters are often an indicator of bad design, and should be avoided. But it's the same with fewer arguments.

A situation like the above get's quite painful if the lambda-body does not even need all those parameters (or is in extreme always returning the same, constant value). But still, the compiler requests me to find unique names for each of the parameters. This is a problem especially when renaming / adding variables "above" the anonymous function which do then interfere with the unused variables there.

Most of the time what I do today is something like this:

someFunction((_, foobar) => ..); //Using '_' as a variable name somewhere around is highly unlikely
someFunction((a, foobar) => ..); //Using short variable names elsewhere is highly unlikely

It also helps reading the code, because I can save a few characters (getting more 'good' code into a line) and make it obvious for readers which variables are important to me, making the code more understandable.

Of course this is somewhat limited, because my "placeholders" are really just ordinary names which must not reoccur. This might lead to something like (_, __, foobar, ___) => .. which starts to look ugly again.

I would like it to have _ (or just another special character, for backwards-compatibility) not usable for ordinary variable names and forbid it's actual usage inside the lambda-body. Therefore it could be used as a real placeholder, enabling something like this:

someFunction((_, _, foobar) => ..);

or even this:

someFunction((foo, _, bar) => bar.otherFunction((_) => foo));

Does this sound like a useful thing to you?

@HaloFour
Copy link

A wildcard character had been described in the C# Pattern Matching proposal which may be up for further discussion for C# 7.0. That character was * which does make more sense as _ is considered a valid C# identifier.

How about also allowing skipping multiple arguments?

void Foo(Func<string, int, bool, DateTime, decimal> func) { ... }

Foo(delegate { return 0m; }) // already valid C# 2.0+
Foo((*) => 0m); // shorthand for above
Foo((s, i, *) => 0m); // capture first two args, skip the rest

If there are overloads which accept Func<>s of differing numbers of arguments the overload resolution would select the one with the fewest remaining parameters:

void Foo(Func<string, int, bool, DateTime, decimal> func) { ... }
void Foo(Func<string, int, bool, byte, DateTime, decimal> func) { ... }

Foo((s, i, *) => 0m); // calls the first Foo
Foo((s, i, *, *, *) => 0m); // calls the second Foo

The one issue here, which also affects normal lambdas, is if there are overloads which accept a delegate with the same number of arguments but only differ by the type of those arguments. The only solution that I can think of there is to require specifying the type of the parameter in the lambda, but that makes the entire concept of catching multiple arguments feel weird.

void Foo(Func<string, int, bool> func) { ... }
void Foo(Func<string, string, bool> func) { ... }
void Foo(Func<string, string, int, bool> func) { ... }

Foo((s, int *) => true); // calls first Foo
Foo((s, string *) => true); // calls second Foo
Foo((s, string *, *) => true); // calls third Foo, but ewww

I think that a wildcard would be useful in a number of other scenarios as well, like the following:

string s = ...;
if (int.TryParse(s, out *)) {
    // Don't need the parsed value, no need to declare an extra variable
}

@Khazuar
Copy link
Author

Khazuar commented Jan 17, 2015

The choice of the wildcard-symbol is not that important. I agree that _ may be a poor choice because it breaks backwards-compatibility and I like * just as much.
Pattern-matching might be an interesting feature, but I think this here might be something independent. It's nonetheless a good idea though to keep the wildcard-symbol consistent.
I like the idea of having a way to also skip all following arguments, although I am not sure this increases readability in the end. An explicit symbol might be useful, so a reader knows there is some magic happening:

void Foo(Func<string> func) { ... }
void Foo(Func<int, int, string> func) { ... }
void Foo(Func<int, int, bool, DateTime, string> func) { ... }

Foo(() => "0"); //->Calls first function
Foo(* => "0"); //->Compiler error, no function with 1 argument available
Foo((*) => "0"); //->Compiler error, no function with 1 argument available

Foo(** => "0"); //->Calls second function
Foo((**) => "0"); //->Calls second function
Foo((i, *) => i.ToString()); //->Calls second function
Foo((*, n) => n.ToString()); //->Calls second function
Foo((i, **) => i.ToString()); //->Calls second function

Foo((i, *, *) => i.ToString()); //->Compiler error, no function with 3 arguments available
Foo((i, *, **) => i.ToString()); //->Calls third function
Foo((i, *, *, *) => i.ToString()); //->Calls third function
Foo((i, *, *, **) => i.ToString()); //->Calls third function

Foo((i, *, *, *, *) => i.ToString()); //->Compiler error, no function with 5 arguments available
Foo((i, *, *, *, **) => i.ToString()); //->Compiler error, no function with 5+ arguments available

The ** can therefore be used to skip all following (at least 1) parameters, choosing the function with the fewest arguments available. ** can only be used as the last argument of the lambda.

I actually have no problem with typing the wildcards. Of course the ** can not be typed, but single-wildcards should not be a problem. This also avoids the ugly example of @HaloFour to be that ugly.

@MadsTorgersen
Copy link
Contributor

This should be thought of as a special case of allowing deconstruction in parameter position, which is something we will consider down the line.

@wcabus
Copy link

wcabus commented Sep 30, 2016

I attended a talk about C# 7 yesterday and saw the tuples and deconstruction features introducing the * wildcard to deconstruct a tuple where you only need certain values and lose the rest. This gave me the idea that it would be nice to also have a * wildcard for expressions, i.e. when dealing with events:

public event EventHandler SomethingChanged;

private void DoStuff() {
    SomethingChanged += (*, *) => { 
        // No need for sender and EventArgs 
    };
}

AdamSpeight2008 added a commit to AdamSpeight2008/roslyn-AdamSpeight2008 that referenced this issue Feb 19, 2017
@gafter
Copy link
Member

gafter commented Mar 20, 2017

We are now taking language feature discussion on https://github.com/dotnet/csharplang for C# specific issues, https://github.com/dotnet/vblang for VB-specific features, and https://github.com/dotnet/csharplang for features that affect both languages.

This proposal corresponds to dotnet/csharplang#111

@gafter gafter closed this as completed Mar 20, 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

8 participants