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

Suggestion: Action method chaining for expression bodies #16961

Closed
mogmios opened this issue Feb 5, 2017 · 17 comments
Closed

Suggestion: Action method chaining for expression bodies #16961

mogmios opened this issue Feb 5, 2017 · 17 comments

Comments

@mogmios
Copy link

mogmios commented Feb 5, 2017

It'd be useful if action methods could be chained jQuery-style so they could be used as an expression. If the compiler understood that any method that returns void should carry on the original instance reference to all following methods in the line the entire chain could be an expression.

With an obj like:
void A () => Do something..
void B () => Do something else..
void C () => Do this too..

So instead of:
void MyMethod ( Object obj ) {
obj.A ();
obj.B ();
obj.C ();
}

We could just do:
void MyMethod ( Object obj ) => obj.A ().B ().C ();

@svick
Copy link
Contributor

svick commented Feb 5, 2017

Do you have a specific type in mind for which this would be useful? Is it really that common that you want to have a method that just calls methods on one object and does not do anything else (no assignments, no conditions, etc.)?

@HaloFour
Copy link

HaloFour commented Feb 5, 2017

Javascript has no such language feature enabling this. jQuery explicitly wrote its functions to behave fluently by returning the appropriate instance. You can do the same in C# today.

@CyrusNajmabadi
Copy link
Member

This would be a nice feature to have. Though i'm worried a bit about this potentially confusing users.

There have been numerous APIs i've worked with that force 'statement' chaining because the types are not fluent themselves. Because it was never legal to call anything off of a void-returning call before, this would not be a break, and would be easy to add into the language in a legal manner.

Personally, I'd be for it. But i'd need to get a sense of what the rest of the LDM thinks. As time has moved on, we've gotten more and more love around making more things possible with expressions, and not requiring so many statements all the time. This would fit into that bucket.

@CyrusNajmabadi
Copy link
Member

@HaloFour I think the idea here would be that this would work even for APIs that didn't take advantage of fluent patterns. I know i've run into this with APIs over the years, and it's been pretty annoying.

@YaakovDavis
Copy link

I wouldn't like . itself to allow chaining voids. Instead, a new operator might be more appropriate, e.g.:
obj.A()..B()..C()

@jnm2
Copy link
Contributor

jnm2 commented Feb 6, 2017

Interesting. This would result in source breaking changes that wouldn't have been breaking before: changing the return type from void to a non-this type.

@HaloFour
Copy link

HaloFour commented Feb 6, 2017

@CyrusNajmabadi

I figured as such, but as jquery was given as an example I thought it important to mention that jquery accomplished through careful use of the existing language features, not through special fluent language features.

As for treating other libraries as fluent, there are a couple of existing proposals to try to tackle this. The proposal above is similar to method cascading, although without the special operator.

However, tangentially, in a number of ways C# isn't very friendly for writing fluent APIs. A lot of APIs rely on property setters and outside of initializers they don't play very well with fluent APIs. LINQ query expressions also hit an awkward wall once you need an operation that doesn't match a LINQ clause.

@mogmios
Copy link
Author

mogmios commented Feb 6, 2017

Not deeply familiar with how the C# compiler would handle it but it seems it'd be more efficient to internally just keep a reference to the instance the methods are on than to actually pass back an object reference for each call.

Instead of a new operator it might make more sense to add a keyword or add a different void-like return type but I think it simpler to just use void. I don't think it'd break any existing code and it'd allow existing code to be used this way without rewrite.

I think jQuery itself is a good reference as to when this form of coding makes sense. LINQ is somewhat similar. As said the style has little to do with JavaScript itself and more to do with being a useful pattern. In some cases it can make code much easier to read and it seems to fit in with the on-going push for using expressions to keep code terse. It doesn't seem nearly as strange to me as having assignments as expressions anyway.

@TessenR
Copy link

TessenR commented Feb 6, 2017

@CyrusNajmabadi

Because it was never legal to call anything off of a void-returning call before, this would not be a break, and would be easy to add into the language in a legal manner.

I believe it would be a breaking change. Currently this code compiles as the lambda parameter 'x' couldn't be of type 'A' exactly because you can't call methods on void. If you enable this the code will become ambiguous.

using System;

class C
{
  static void Main()
  {
    M(x => x.M1().M2());
  }
    
  static void M(Action<A> a) {}
  static void M(Action<B> a) {}
}

class A
{
    public void M1() {}
    public void M2() {}
}

class B
{
    public B M1() => this;
    public void M2() {}
}

@Joe4evr
Copy link

Joe4evr commented Feb 6, 2017

but it seems it'd be more efficient to internally just keep a reference to the instance the methods are on than to actually pass back an object reference for each call.

I'm pretty sure return this; is no less efficient than keeping a reference at the callsite.

@CyrusNajmabadi
Copy link
Member

@TessenR Good point. But, in general, we've never taken the "if could cause an overload resolution in error in lambdas with overloads" to justify not doing something. The reason for this is that it prevents us from ever taken something illegal and making it legal. We think that's too onerous, and too unlikely, to warrant having it interfere with language progress.

@DavidArno
Copy link

DavidArno commented Feb 6, 2017

I skimmed through the OP and got to the line:

void MyMethod (Object obj) => obj.A().B().C();

and thougth, "the language already supports that", before realising that A, B and C were void. This makes it a deeply confusing language change.

I like @YaakovDavis's suggestion of a brand new syntax for this proposal though as it avoids that confusion.

@CyrusNajmabadi
Copy link
Member

I like @YaakovDavis's suggestion of a brand new syntax for this proposal though as it avoids that confusion.

I personally don't like this. But it mirrors what we see nearly all of the time when we change the language. Any time we add something new, we often get a huge amount of pushback asking for some new piece of feedback to make it clear what was going on, and to help avoid the language being "deeply confusing". However, as people become comfortable with a language feature then they start feeling the opposite. The don't want hte differences called out. And they want things to just feel the same as the current language.

A great example of that was extension methods. People were literally flipping out (negatively) when we added them. After all "obj.Foo()" was now a static call to Foo with 'obj' as the first argument. To some people, this language change was so awful and so unacceptable, that they believed they could not use C# again, and that the community would never accept this in the simple . form.

So, while i agree that this would be a 'change' from the language as is. I don't see why it necessarily needs to be confusing. Once people learn, then this will just be C#, and it can be totally fine to be reusing the bog standard syntax that people are used to in a fashion that can make total sense in any reasonable coding context.

@YaakovDavis
Copy link

YaakovDavis commented Feb 6, 2017

If you could do the following, then the dot syntax makes sense:

var x = obj.A(); //A() returns void
x.B(); //x referencially equals obj

Otherwise, the dot operator loses its consistency.

Also, void methods are usually void for semantic reasons.
The OP stated a JS class which was fluent to begin with. I'd argue that non-fluent APIs generally can't just become fluent by piping. Instancing, state handling, etc needs to be handled as well.
Let's see a motivating example that calls for such a change.

@Joe4evr
Copy link

Joe4evr commented Feb 6, 2017

The .. syntax has been proposed on here before in the Method Cascading issue linked by HaloFour. I wasn't opposed to it back then, but thinking about it now, not so much. Much of this problem can be solved by designing an API in a functional manner and make mutating operations return the instance instead of void. If it's a third-party API, you could request the authors to change to this principle, or you could define some extension methods that would do it for you.

After all, I'm not so sure one of the design principles of C# is to compensate for the discrepancy between how an API is designed and how a consumer would like to use it.

@CyrusNajmabadi
Copy link
Member

After all, I'm not so sure one of the design principles of C# is to compensate for the discrepancy between how an API is designed and how a consumer would like to use it.

I disagree. For example, we added object-initializers so that people could work with APIs where you had to create an object, and then use a bunch of statements to initialize it.

A lot of our language design is precisely around making existing APIs be much nicer to work with in the language.

@jcouv
Copy link
Member

jcouv commented Oct 22, 2017

A csharplang issue was created to continue this discussion: dotnet/csharplang#781
I'll go ahead and close OP. 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