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] Allow extension methods to be used in the infix position #3673

Closed
metatron-the-chronicler opened this issue Jun 24, 2015 · 13 comments

Comments

@metatron-the-chronicler

It would be great if arbitrary functions defined on extension methods could be used in the infix (or postfix) position.
This would solve requests like #100 and make it possible to create cleaner dsls.

Technical

  • Infix functions would be resolved by name and/or an attribute annotation. This could also be used to setup aliases for query style Linq expressions so that names within that context could be made consistent.
  • It probably makes sense to restrict infix function usage to extension methods that take a minimum of two arguments.

Consider:

namespace Sample
{
    public static class Example
    {
        public static string FancyStringTransformation(this string argument,string argument2)
        {
            //Stuff here.
        }
    }
}


//Usage
string someString;
string someString2;

var t = someString FancyStringTransformation someString2;

Given this example this is the same as

var t = someString.FancyStringTransformation(someString2);

If there are more arguments they must be given default values or there must be a valid overload that satisfies the minimum two arguments.

Postfix functions

  • Any function that is to be used in postfix form must have zero or one arguments at a minimum.
@HaloFour
Copy link

I'm not sure that I see much benefit to this proposal. It doesn't really solve the LINQ extensibility issue as the scope under which LINQ queries execute is very different from extension methods (as you need to operate on the range, not the query). Beyond that it just appears to be trying to force a very non-C# syntax on top of C#.

@svick
Copy link
Contributor

svick commented Jun 24, 2015

@HaloFour I believe the intention is that you could write something like:

from x in list
select x ToList()

Which would be translated to:

(from x in list
select x).ToList()

But that's the only place where I think this would make sense (and even then the syntax is not great; also it wouldn't work well for methods with lambda parameters like ToDictionary()). But given how big change of syntax this would be and the relatively small benefit, I agree this proposal would not be a great addition to C#.

@metatron-the-chronicler
Copy link
Author

@svick Why wouldn't this work with lambda parameters?

@HaloFour What do you mean by operating on the range as opposed to the query?

@svick
Copy link
Contributor

svick commented Jun 25, 2015

@metatron-the-chronicler What I mean is that with such methods, you want to be able to reference the range variable directly, without using the lambda syntax (similar to how you can reference them in where or select).

For example, using a variant of the apply syntax from another proposal (#3571), it might look something like (imagine that the query is actually more complicated):

from person in people
select person
apply ToDictionary(person.Id, person.Name)

Using your syntax, you would write:

from person in people
select person ToDictionary(p => p.Id, p => p.Name)

@metatron-the-chronicler
Copy link
Author

@svick I see. wouldn't such a case be solvable with let though?

from person in people
let g=people ToDictionary(person.Id,person.Name)
select g;

@svick
Copy link
Contributor

svick commented Jun 25, 2015

@metatron-the-chronicler Unless you're somehow completely changing the semantics of let, that looks like you're creating a sequence of dictionaries, one dictionary per item. What I want is a single dictionary, with one key value pair for each item.

(Also, I can't see how could that compile anyway, since the ToDictionary() parameters need to be lambdas and let can't create lambdas like that.)

@metatron-the-chronicler
Copy link
Author

@svick I thought we were talking about supposed syntax it would work fine if we used the real one which does require lambdas. The problem with your example is that you need to either declare a new dictionary in a projection or you need to use the actual collection since ToDictionary is defined on the source collection and not the temporary variable. You could also define a ToDictionary method directly on the person type to get this to work. Even with the current syntax your expression wouldn't produce the desired results because you need to generate the sequence of dictionaries and then take one.

from c in collection
let g=collection ToDicitionary(c.id,c.name) //in real life you would use the lambda expression to access the properties you want need to be a lambda expression unless you made an overload for this kind of syntax.
select g Single

@svick
Copy link
Contributor

svick commented Jun 25, 2015

@metatron-the-chronicler Even with modified syntax, let doesn't make any kind of sense for this to me and I can't imagine how would it actually work. let is a projection, i.e. it is executed per item. But that's absolutely what you don't want to do here, you want to execute ToDictionary() only once.

@metatron-the-chronicler
Copy link
Author

@svick The thing is that the query expressions will always return some kind of Enumerable. Select (map) applies some function over a collection. You can't do what you are trying to pretty much by definition. The function you are applying to the sequence is (In this case at least.) logically created by all statements between from and select.

In order for what you want to do to work you would have to treat the entire query expression as the argument to ToDictionary and take one element because ToDictionary would be applied to the generated Enumerable. You have to resolve the left hand expression to T from Enumerable regardless unless you do magic.

@svick
Copy link
Contributor

svick commented Jun 25, 2015

@metatron-the-chronicler

You can't do what you are trying to pretty much by definition.

I believe I can, for example using the apply syntax I showed above. More importantly, VB.NET LINQ can already do this: in it, you can write:

Aggregate person In people
Into ToDictionary(person.Id)

Though this doesn't work:

Aggregate person In people
Into ToDictionary(person.Id, person.Name)

In order for what you want to do to work you would have to treat the entire query expression as the argument to ToDictionary and take one element because ToDictionary would be applied to the generated Enumerable.

Yes, that's exactly what I want. Just like where condition is translated into a call to Where that's applied to the Enumerable, I want apply ToDictionary(person.Id, person.Name) to be translated into a call to ToDictionary applied to the Enumerable. Why would that be a problem?

@metatron-the-chronicler
Copy link
Author

@svick your Aggregate example is fundamentally different from the one using select Aggregate returns T rather than Enumerable Select returns Enumerable that's why it doesn't make sense without first getting something that returns T.

when you write

from c in collection
where c.id >1
select c

you are actually saying that you want a sequence that has had some function applied to the source collection. In the case of the above the function to be applied is a boolean that determines which elements have ids with values greater than one.

Aggregate does something different. It takes elements and applies a function to them and produces a single result.

With the apply example earlier apply has to put person into scope but it would still generate a sequence as opposed to a single dictionary value unless apply basically changes the whole meaning of the left operand (The query expression.)
It pretty much ends up doing what let does for query expressions.

With this proposal the special context produced by from is no longer necessary. You could write

var result=collection 
Where (x=>x.id<500) 
Select (r=> new{ some shaped result here}) Single

The only thing to figure out is if it is possible without totally breaking everything to relax the requirements for parentheses and explicit arrows (x=>x) for lambda expressions. Though that is entirely optional.

@gafter
Copy link
Member

gafter commented Oct 20, 2015

I think we'd be more likely to take #5445 instead of this proposal.

@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.

@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

4 participants