This has come up a few times on CodePlex as well as here but I've not seen any formal proposal for it so I thought I'd post one just to track it and get some conversation going about it.
Many of the LINQ extension methods accept a single argument and return a value, most often the result of a simple expression stemming from that argument. In those simple cases the developer is still required to assign the parameter value to a name that cannot shadow a name used in the current scope and then immediately reference that name in the body of the closure.
var query = db.Orders
.Where(order => order.Value > 50.0m)
.Select(order => order.Employee)
.OrderBy(employee => employee.Name);
I propose that a short-hand version of this common pattern where the value of the argument can be accessed through a specific sigil which must be referenced in the method body. The following uses the sigil @
and is the same as the query above:
var query = db.Orders
.Where(@.Value > 50.0m)
.Select(@.Employee)
.OrderBy(@.Name);
:+1:
While my mind is not settled about wether this syntax is cool or ugly, let's think about these problems first:
var query = db.Orders
.Where(@.Items.Any(@.Weight > 20));
instance.Do(@.SomeMethod(() => @.SomeProperty));
Is () => @.SomeProperty
now passed as argument-less lambda or is it a lambda with one anonymous @ argument that returns lambda with a closure on @, e.g.:
instance.Do(@.SomeMethod(@ => () => @.SomeProperty));
var query = db.Orders
.Where(@.true); //? member access, no
.Where(@true); //? rather like this, though **extremely** small difference to verbatim identifier / strings
.Where(true); //? not unique enough, overload resolution problems
var query = db.Orders
.Where({
return @.Items.Any(x => true);
});
@Suchiman
I agree, it can get nasty quick. My personal opinion is that none of that is permitted and the sigil is effectively just expanded into _temp => _temp
. Trying to build a fully-featured alternate syntax defeats the purpose of the short-hand and the existing lambda syntax is more than sufficient and plenty succinct.
To be a little more specific, the sigil would not represent an actual variable in scope. It could not be reused within the expression in any form. That would make nesting permitted and unambiguous (and a common scenario where I see this being useful), and the other situations non-issues since none of them would be legal.
That's my personal opinion, though.
Also mentioned in #3171
Swift calls this Shorthand Argument Names and uses the syntax $1
for the first argument, $2
for the second, etc.
@lawrencejohnston Sorta, I'm not looking to reproduce Swift's syntax in the least. I actually really don't care for the idea of having some kind of indexed sigil or implicit tuple for referencing the arguments. As mentioned above the only time that I think that this syntax should apply is for those very simple cases where you need an expression rooted in the argument, otherwise the sigil cannot be used.
@HaloFour If C# ends up going a different direction that's fine, I just wanted to put Swift's similar feature out there as a source of inspiration/discussion.
@lawrencejohnston You're right. I put this proposal up specifically to generate conversation so it's silly to quash it with my opinion. :smile:
@Suchiman, statement-bodied lambdas should definitely not use this syntax. Nested lambdas are also a no-no (only innermost lambdas should be allowed to use @
). Only simple chains should be allowed:
var data = File.ReadAllLines(@"in.txt")
.Select(@.Split('\t'))
.Select(new {Word = @[0], Freq = Double.Parse(@[1])})
.SelectMany(
w=>LetterCombinations(w.Word)
.Select(new {LC = @, Freq = w.Freq})
)
.GroupBy(@.LC)
.Select(g => new {LC = g.LC, Freq = g.Sum(@.Freq)})
.OrderByDescending(@.Freq)
.Select(String.Format("{0}\t{1}", @.LC, @.Freq));
File.WriteAllLines(@"out.txt", data);
Of course, now we'll have _four_ different ways to write a delegate...
I would prefer that
or its
keywords for this kinda situation. for example .Select(that.Name)
.
@alrz
IIRC C蠅 used an it
keyword. Is there a benefit to requiring a new contextual keyword? Seems that it would complicate the grammar since the compiler would be required to determine if a variable/type/etc of that name was in scope (as it does with var
, dynamic
, etc.) and it requires more keystrokes than a simple sigil.
Have you thought about the case of passing Func<int, int>
to something that expects Func<int>
? The longhand version is () => func(x)
, what would the shorthand version be, @func(x)
?
@orthoxerox
I have not. This proposal only really addresses those delegates that accept a single parameter and return the result of a single expression. Is there a reason that the expression () => func(x)
isn't DRY or succinct?
Not a bad idea. Maybe even a shorter syntax could work, where a dot with no LHS is used to access a member of the implicit parameter:
c#
var query = db.Orders
.Where(.Value > 50.0m)
.Select(.Employee)
.OrderBy(.Name);
Off the top of my head I can't think of any reason that would be a problem.
This would still have some limitations, each shared with the original proposal:
@bondsbw: There might be a problem if you want to test the value itself.
var evens = myNumbers.Where(@ % 2 == 0);
will work, but your syntax won't.
This is neat, I like it. But could it be @0 ? So we could @1 / @2 if it is multiple parameter
I would like to suggest # or $ too in addition to .
And could this be possible to use in normal function? We may like to change the name of param for documentation in intellisense but want to shorthand in the code
@Thaina short answer: no
@ In this context is designed to make lambdas in very specific scenarios shorter where you don't care about the name anyway since it's clear what the name will be.
In regular Methods, this would lead to totally unreadable code, nothing is stopping you from naming your arguments p0, p1, p2 but nobody does for OBVIOUS reasons...
@Suchiman In some case parameter name was included in public human readable intellisense so we may want to make it long and readable like
C#
public T GetByIndex(int indexOfItemInTheList) // This will be shown in intellisense
{
return list[indexOfItemInTheList];
}
But then it make the code more messy like that
Normally I would temp it to int i which I don't like to add variable to do just that. In worse case if it is large struct that copied it will be bad
Don't like the syntax but I really like the idea.
Personally, I'd go with Swift version as @lawrencejohnston said, simply because it's more flexible even though I know this proposal is designed for the _simple_ case.
@Suchiman Sometimes the predicate can be simple and yet depends on two/three parameters, I see no reason to restrict it to only a single parameter, a single parameter doesn't imply _simplicity_.
I noticed recently that a "discard" syntax is being added so people will be able to write this:
if (TryGetValue(out var _))
{
// `_` is not a real variable and will not show up in IntelliSense
Console.WriteLine("Has a value, but we don't want to know what it was.");
}
Since it looks like _
is already being repurposed, maybe we could use it as shorthand for the lambda variables too:
var query = db.Orders
.Where(_.Value > 50.0m)
.Select(_.Employee)
.OrderBy(_.Name);
If there was already an existing variable _
in scope then it would just bind to that variable.
@jamesqo
I don't think that would be possible given that it could change the behavior of existing code where a variable named _
is already is scope. The use of _
as the discard variable is frought with enough technicalities which will likely frustrate developers with compiler errors (or silent data loss) as it is.
@HaloFour
I don't think that would be possible given that it could change the behavior of existing code where a variable named _ is already is scope. The use of _ as the discard variable is frought with enough technicalities which will likely frustrate developers with compiler errors (or silent data loss) as it is.
Correct me if I'm wrong, but isn't discard contextual, i.e. it will preserve existing behavior if something named _
is in scope?
If I am, I vote for one of these (with positional parameters, which I just saw above):
db.Orders.Where(#1.Value > 50.0m); // Or #.Value > 50.0m
db.Orders.Where(&1.Value > 50.0m); // Or &.Value > 50.0m
db.Orders(@1.Value > 50.0m);
db.Orders(\1.Value > 50.0m);
$_
is used for a similar purpose in Powershell. I could also see $1
, $2
, etc. for positional.
Actually, wouldn't positional parameters be ambiguous? For example, db.Orders.Where($1.Value > 50.0m)
could call either the standard overload, or the indexed overload where the index is ignored.
@jamesqo
You are correct that _
does retain it's previous behavior as a proper identifier if there is a variable or field of that name in scope. That's one of the things that I find unsavory about it; it can quickly be confused as either a wildcard or an identifier depending on the code that comes before it. In my opinion it's too overloaded and I'd prefer to not stretch the contexts under which it can be used further than it already is. At least with declaration expressions the compiler will always treat _
as a wildcard. It's the short-form that suffers from the ambiguity.
Using _
as the sigil for such lambdas is probably a little safer since it doesn't resemble lambda syntax today, but the compiler would be forced to first attempt to resolve any identifiers by the name _
and then resolve overloads of the method called accepting the result of that property access, so for .OrderBy(_.Name)
the compiler would have to first try to resolve .OrderBy(string)
. Given libraries like Dynamic LINQ those might not be terribly uncommon beasts.
How about combining simple sigil suggestion with the swift functionality. If the sigil is used alone that it's tightly coupled and cannot propagate but if the sigil is follow by a number then it can.
I don't think positional parameters like $1 are a good idea in C#. They would provide a second fully-featured way of writing lambdas, while being vastly inferior to the current syntax (see nesting, overload resolution) and less readable.
Instead, I like the spirit of the original proposal: A shorthand for cases where indexing or naming parameters is not needed (because there is only one parameter).
This is now being discussed at ~dotnet/csharplang#74~ dotnet/csharplang#91
Actually the conversation directly moved to https://github.com/dotnet/csharplang/issues/91. This issue should be closed in favour of that one.
Sorry for the mistake!
Closing since the issue was moved to csharplang. Thanks
Most helpful comment
I noticed recently that a "discard" syntax is being added so people will be able to write this:
Since it looks like
_
is already being repurposed, maybe we could use it as shorthand for the lambda variables too:If there was already an existing variable
_
in scope then it would just bind to that variable.