Roslyn: Proposal: Forward Pipe Operator

Created on 25 Sep 2015  ·  125Comments  ·  Source: dotnet/roslyn

When you are chaining multiple methods to one another you might end up with something like this:

``` C#
Console.WriteLine(BitConverter.ToString(SHA1.Create().ComputeHash(File.ReadAllBytes(Console.ReadLine()))));

Using forward pipe operator it can be written as (in the same order that they will be executed):

``` C#
Console.ReadLine()
|> File.ReadAllBytes()
|> SHA1.Create().ComputeHash()
|> BitConverter.ToString()
|> Console.WriteLine();

We can take a step further and specify named args (similar to currying proposed in #3171):

``` C#
Console.ReadLine()
|> File.ReadAllBytes()
|> SHA1.Create().ComputeHash()
|> BitConverter.ToString()
|> Console.WriteLine(format: "SHA1: {0}");

Since C# is not exactly a functional language, forwarding to the last parameter wouldn't be useful most of the time, because not every method parameters are written with a sensible order for currying purposes. Also, in functional languages we don't have currying and overload resolution at the same time, that is, functions are often numbered like `iter1`, `iter2`, etc. In C#, however, we can mix and match overload resolution and optional and named arguments to be able to use forwarding operators in a wide variety of use cases without introducing any other operators.

Applicability of argument lists in RHS will be defined as follow:

**Empty argument list:** It's a compile-time error if the method in RHS doesn't accept any parameters. I suggest the argument list to be not optional, otherwise it will be inconsistent when we actually do have an argument list. RHS will be not evaluated as a general expression, so if you want to forward to a delegate, you will be required to write parentheses in front of it.

``` cs
Action<int> a = q => {};
arg |> a;       // ERROR

void M() {}
arg |> M();     // ERROR

Positional arguments: Each positional argument will be matched in order to the list of method parameters. If there was more positional arguments than number of parameters minus one and the last parameter was not params, the method is not applicable. Otherwise, the LHS goes to the last element in the expanded form.

void M(int a) {}
arg |> M();     // M(arg);

void M(params int[] a) {}
arg |> M();     // M(new[] { arg });
arg |> M(1);    // M(new[] { 1, arg });
arr |> M();     // M(arr);

Evaluation order: The LHS will be evaluated in the lexical order, i.e. first.

F() |> M(G());  // var t = F(); M(G(), t);

Optional arguments: In case of optional arguments, LHS goes to the leftmost unspecified parameter which has the identical or implicitly convertible type of LHS. If there was a more specific parameter, then we skip other less specific ones.

void M(int a, int b, double c = 0, int d = 0) {}
4d |> M(2, 3);  // M(2, 3, 4d, 0);
4  |> M(2, 3);  // M(2, 3, 0, 4);

Named arguments: Each named argument will be matched to a parameter with the given name. If one of the named arguments failed to match, or matches an argument already matched with another positional or named argument, the method is not applicable. Otherwise, we'll do as above.

void M(int a, int b, int c) {}
void M(int a, double b, int c) {}
1  |> M(2,  c: 3);      // M(2, 1, 3);
1d |> M(a: 2,  c: 3);   // M(2, 1d, 3);

The method is not applicable (1) if more than one of non-optional parameters are not specified, (2) LHS was not implicitly convertible to its type (3) or it's a ref or out parameter.

Lambda expressions: You can forward to a syntactic lambda if you want to explicitly name the forwarded value, e.g.

Console.ReadLine()
|> File.ReadAllBytes()
|> SHA1.Create().ComputeHash()
|> bytes => { foreach(var b in bytes) Console.Write($"{b:x2}"); };

No delegate will be created and the lambda will simply elided just as you were wrote:

    foreach(var b in System.Security.Cryptography.SHA1.Create().ComputeHash(File.ReadAllBytes(Console.ReadLine())))
        Console.Write($"{b:x2}");

Null-conditional forwarding operator

(Moved from #8593)

It has been suggested by @HaloFour to extend this with a variant that function like the null-propagation operator, as an alternative to #5961,

var r = Foo.Bar?.Baz ?> F() ?? false;
var r = ((temp = Foo.Bar?.Baz) != null ? F(temp) : null) ?? false;

Function F won't get executed if the forwarded value was null, and also, Foo.Bar?.Bar only evaluates once. Note that the value forwarded to the target function F is of a non-nullable type (#5032).

Just like ?. operator, you don't need to use ?> if the target function doesn't return a nullable value, so for chaining you should use the regular |> operator to not perform an additional null-checking, e.g.

var r = nullable?.Foo.Bar ?? value;
var r = (nullable != null ? nullable.Foo.Bar : null) ?? value;

var r = value |> F() ?> G() ?? value;
var r = ((temp = F(value)) != null ? G(temp) : null) ?? value;

var r = nullable ?> F() |> G() ?? value;
var r = (nullable != null ? G(F(nullable)) : null) ?? value;

var r = nullable ?> F() ?> G() ?? value;
var r = (nullable != null ? (temp = F(nullable)) != null ? G(temp) : null : null) ?? value;

var r = value |> foo?.F();
var r = foo?.F(value);

var r = nullable ?> foo?.F();
var r = nullable != null ? foo?.F(nullable) : null;
2 - Ready Area-Language Design Feature Request

Most helpful comment

I'm actually going to be contrarian and say I would like and prefer |> over .. and -> as both of those have other very clear definitions in my head coming from other languages.

All 125 comments

You already mention #3171 so you must be aware that this is a dupe, except with -> instead of |>.

The symbol -> is already an operator in C#, but the concept could be done with a different syntax.

I love it The code is already sequential and syntax should imply that.
How would you combine this with asynchronous methods?

@tpetrina That wouldn't be a special case, just an await before the method name will do the job. In case of instance methods you would take advantage of instance method delegates (#5444):

C# // Not a useful example but just for demonstration: string result = new StreamReader(path) |> await StreamReader::ReadToEndAsync

And as @gafter mentioned -> is already an operator so I think the closest alternative for this, is to borrow |> from F#.

Now that looks quite F#ish. Shouldn't you rather write:

string result = await
    new StreamReader(path)
    |> await StreamReader.ReadToEndAsync

Can you do this?

Task<string> resultTask = 
    new StreamReader(path)
    |> StreamReader.ReadToEndAsync

Now that looks quite F#ish.

That's exactly what I was afraid of when I proposed ->!

The former with two awaits doesn't make sense but the latter is correct. The problem is, when you want to pass the value (rather than Task<> itself) to the next method you should first await it:

new StreamReader(path)
|> await StreamReader.ReadToEndAsync
|> Console.WriteLine

I see, so you men the code above is equivalent to the following:

Console.WriteLine(await new StreamReader(path).ReadToEndAsync());

I really like the idea, finally a way to write simple things in a simple manner. :)

The second syntax |> isn't as great as -> in my opinion so maybe double dots?

1st Example

Console.ReadLine()
.. File.ReadAllBytes
.. SHA1.Create().ComputeHash
.. BitConverter.ToString
.. Console.WriteLine;

2nd Example

Console.ReadLine() .. File.ReadAllBytes .. SHA1.Create().ComputeHash
.. bytes => foreach(var b in bytes) => Console.Write($"{b:x2}");

I'm actually going to be contrarian and say I would like and prefer |> over .. and -> as both of those have other very clear definitions in my head coming from other languages.

@eyalsk .. looks more like a special kind of member access operator like Dart's cascade and does not imply _forwarding_. Besides, it is better suited for ranges, if we ever wanted to introduce them in the language.

@RichiCoder1 I'm not a fan of copying the exact same syntax from other languages, the reason that F#'s using |> is that it uses pipe in a lot of syntaxes like discriminated unions, pattern matching, backward pipe, etc and |> just makes sense in that context and it's consistent. on the other hand, using it in C# makes it really F#ish.

I suggest :> as an analogous to the named _agruments_, since the value will forward to a _agrument_ in the next method, so

Console.ReadLine()
:> File.ReadAllBytes
:> SHA1.Create().ComputeHash
:> BitConverter.ToString
:> Console.WriteLine(format: "SHA1: {0}");

In the last line you can see what I mean.

@alrz I understand.

I like this version :> a lot more than the pipe version, to me it looks better.

Personally I cannot stand code like this:

C# tuple :> dictionary.Add;

And I find this idea even more distasteful:

C# var sumOfEvensSquared = xs :> Enumerable.Where(x => x % 2 == 0) :> Enumerable.Select(x => x:> Math.Pow(x)) :> Enumerable.Sum;

I would like to see better support for function composition in C#, and if extension methods could be applied to the majority of Method Groups, perhaps via a costly but useful implicit conversion to a Func or Action, a lot of this could be accomplished nicely as a fluent DSL by adding extension methods like Compose and AndThen to these delegate types.

Anyway, If something like this is adopted, please do not go with this F# style syntax. It does not fit well into the language and frankly not even all F# enthusiasts find it readable. See: http://fsprojects.github.io/FSharp.Core.Fluent/

Of course, this is just my opinion, but many of F#'s syntactic constructs are not a good match for integration into C#.

This is partly due to the fact that F# was designed to be a "Functional First" multi-paradigm language, and partly due to the fact that the OCaml syntax is radically different from C#.

Anyway, I think Scala is a better language from which to draw inspiration when adding functional style features to C# because it was designed to _merge_ FP and OOP.

Just as food for though It's also interesting to consider Douglas Crockford's lecture series on Monads where he argues that Monads are easier to understand using method notation. It's quite interesting: https://www.youtube.com/watch?v=dkZFtimgAcM

Function composition would be more powerful than the limited query operators in LINQ. I think it would even eliminate the need for LINQ.

I don't envision another way of composing functions other than using delegates (Func<> or Action<>).

A chain of compositions on the same statement could be optimized to a single delegate, but across multiple statements, I don't think it could.

@aluanhaddad You are not supposed to use it wherever you can, but in some cases it causes to save some temporary variables along the way without sacrificing readability. And don't even compare this to "function composition" it's nothing like that. I don't understand how are you suggesting that "function composition" could be more useful than this in C# while you're mentioning that it's not "functional first" in the next sentence.

Please don't use <: or :>, they're standard notation for subtyping in general PLT. It would be awfully confusing to use these symbols for function application.

@mausch I would like to hear what are you suggesting _instead_.

@alrz I don't particularly care about this proposal as long as it doesn't pollute the language with confusing notation...

@alrz F# and Elixir both use |>, so it would seem you have prior art from which to draw across multiple platforms.

Also, I agree with @mausch. I cannot look at :> without thinking of an upcast. I find that highly confusing.

ocaml-core and Scalaz use |> as well.

@mausch @panesofglass Yes, that was first notation that I could think about, but don't you think it makes it really F#ish? That's all that I'm afraid of, otherwise, nothing's wrong with |>.

I would be surprised if we did this in C# with anything other than |>.

@alrz we've shown examples of languages other than F# using the same operator with the same meaning, so the operator isn't really "f#ish" any more.

Not sure what's wrong with being "f#ish" BTW. But as I said, I don't particularly care about this, so I'm unsubscribing now...

I agree with the others, the operator should definitely be |>, not :>!

@gafter Will the precedence be lower than _query-expression_ so that this can be possible?

var result = from item in list select item.ToString() |> Distinct();

Assuming using static System.Linq.Enumerable; at the top of the file.

@alrz Excellent question. It isn't clear what the precedence _should_ be.

@alrz,

var result = from item in list select item.ToString() |> Distinct();

Assuming using static System.Linq.Enumerable; at the top of the file.

Distinct is an extension method and it's not pulled to the global context by using static.

But maybe it should, on this case.

@paulomorgado Oops. Didn't know that.

An extension method is a regular static method.

Quoting C# specification. Any particular reason for this?

PS: It makes me die a little inside when someone quotes my comment without the exact formatting.

You'll need to see the specification for using static which I don't think is availble yet.

PS: ?

@alrz as @mausch and I noted, |> is not strictly F#-ish, nor do I understand how that is a concern. Async/await is also F#-ish, as are many other features now in or coming to C#. Isn't it better to prefer consistency across languages rather than confuse with differing operators? F# also has +, *, -, and / operators. Should C# adopt different operators now? I think we would all agree that is unreasonable. I would argue the same impulse holds for pipe forward.

@panesofglass are you implying that all languages should follow the same syntax? how does this even make sense? I mean why C# cannot be inspiration to others languages? why F# and Scala needs to be languages to derive inspiration from? as much as I love Scala, I think what you guys are doing here is wrong, you're trying to convey people that it's the right thing because other languages use this operator or suggests that because this other operator :> is used in a different language it doesn't fit! that's just weird.

The only two _real_ reasons here are as follow:

  • The symbol needs to imply forwarding as @alrz told me.
  • Taste, you either like it or you don't.

C# is neither evolution of F# nor Scala and it is my opinion that it's perfectly fine to have a different syntax.

I like consistency but achieving consistency across languages is in my opinion moot point.

Personally, I'd go with anything but pipe like ~> or :> I don't like pipe, simply because on my keyboard the tilde and colon are more accessible to me but then you can probably say the same.

@eyalsk my point was not that C# should adopt features or syntax directly from Scala, I only mentioned Scala because it has many functional features and its syntax is closer to C#'s making it a better source from which inspiration can be drawn for functional feature syntax than F#. Specifically I brought this up because I think more. Net programmers are familiar with F# and there seem to be a lot of proposals which go directly to F# for syntactic inspiration.
You're absolutely correct. And indeed, C# has been and continues to be a source of inspiration for many other languages including F# and Scala.

@aluanhaddad I understand, yeah, F# is well known in the .NET community, and Scala is definitely awesome but what I mean is that we shouldn't and I really hope that they will find the right symbol for it, I understand that it shouldn't be used a lot but it doesn't mean it should be _fugly_ or inconvenient but I guess that it's already decided and they are going with it, right @gafter?

@eyalsk I'm not arguing for following syntax, which I agree would be ridiculous. I'm arguing for using similar operator symbols where possible. Repurposing operators is a source of confusion to newcomers. We have the same problems in existing F# libraries where operators that don't match expectations in other languages create a problem for new users. See https://github.com/SuaveIO/suave/issues/311. I'm in this discussion only to help you avoid a similar situation in the future. You are free to ignore me, and as I assume you would prefer, I will at this point bow out.

FWIW, I think a better C#-oriented solution would be to find a way to adapt to using the ., which extension methods already provide. In some ways, I find this proposal really weird, as it introduces a paradigm foreign to the classic C# style.

@eyalsk

Personally, I'd go with anything but pipe like ~> or :> I don't like pipe, simply because on my keyboard the tilde and colon are more accessible to me but then you can probably say the same.

Yes, in that case we should probably use Guillemet », aka "french right quote". That's even better because it is a single character. It is also really easy to type. On Windows hold down the alt key and hit 175 on the numeric keypad, then lift the alt key.

Alternatively perhaps I can ship you a keyboard.
</sarcasm>

@gafter, on my keyboard, » is Shift+[the key just left of backspace or two key right of 0]. It's as hard as typing >, ?, ! or ". I'm all for it!

@gafter I'm using multiple languages on my keyboards so it would be just an Alt+Shift away. Anywho, now that you brought it up, I'd say we can go with the notation for function composition. God that is familiar.

@panesofglass

Isn't it better to prefer consistency across languages rather than confuse with differing operators?

If we're going to adopt other languages' operators, just because they are "consistent across languages", how about _Mathematica_ which is using // operator for postfix function application. It just makes the following function a comment; wouldn't be a problem as long as it's familiar and consistent.

@panesofglass I understand where you're coming from and no I wouldn't ignore you just because we share different opinions.

I agree that my initial suggestion was out of place and @alrz already said to me something about it and I stand corrected, I have no problems of being wrong.

@gafter it's funny how you take one person's opinion and make fun of it just because you can where I actually made an honest opinion about it.

If you actually want to make people laugh go work at the circus or something, don't try to use sarcasm and underestimate people's opinion, it's rude.

I look forward to the day that I need an APL keyboard in order to program in C#.

↑1 ⍵∨.^3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵

@HaloFour So familiar. "newcomers" would love that, as @panesofglass said.

@panesofglass

In some ways, I find this proposal really weird, as it introduces a paradigm foreign to the classic C# style.

I agree. Functions work very differently in F# and other functional languages. Not to mention this proposal is incomplete. There are four operators in this family. Clearly if the forward pipe is being considered then the backward pipe should also be on the table. And forward/backward composition.

@alrz

So familiar.

Once per proposal. :grinning:

In some ways, I find this proposal really weird, as it introduces a paradigm foreign to the classic C# style.

I disagree. Pattern matching, ADTs, LINQ, etc come from a functional background, as long as C# is embracing functional paradigm features it wouldn't be "weird" However you can stick to the classic C# style since none of these are mandatory to use. I smell _neophobia_ by the way.

Those features/concepts come from those other languages, but the syntax was adapted to fit with C#. In this case you're just duplicating functionality already directly available in the language through F# syntax.

How about Merge branch fsharp/fsharp into master then? My point is all of these features should be reconsidered before adapting them for C#. As I said, F# is using pipe for a lot of things, and |> makes sense in that context; but in C#, pipe is just used for logical and bitwise _or_, I guess. I suggested :> because it would make sense in C# code. I don't care about other languages syntaxes here, nor about "newcomers"; if that's the case, all languages should have similar syntaxes for sake of newcomers.

@eyalsk

@gafter it's funny how you take one person's opinion and make fun of it just because you can where I actually made an honest opinion about it.

If you actually want to make people laugh go work at the circus or something, don't try to use sarcasm and underestimate people's opinion, it's rude.

Do you mean you were being serious when you proposed that a language used by millions be designed around the keyboard layout you happen to be using now?

@alrz The challenge C# is facing now is integrating some FP features in a coherent manner. You can't just bolt on a random subset of these features into the language without 1) affecting it's character and 2) risking confusing the language even further into some hodgepodge of incoherent OO + FP feature set. This is unlike Scala which, for any of its faults, was designed to be an OO/FP blend from day one. That's not C#. Remember when LINQ first came out, how it divided many developers (and to this day I maintain that there are two types of C# devs - C#3+ devs and those that stopped at C#2)?

I find it interesting that you accuse someone who uses both C# and F# of neophobia - I happen to agree that features like this really look strange within the context of C# - using the . operator in some way would seem a more natural fit. Thinking about it further, without currying as a first class language feature, I'm not even sure how useful this feature would be anyway.

The challenge C# is facing now is integrating some FP features in a coherent manner. You can't just bolt on a random subset of these features into the language without 1) affecting it's character and 2) risking confusing the language even further into some hodgepodge of incoherent OO + FP feature set.

I didn't understand if this is a hodgepodge of incoherent OO + FP or the challenge C# is facing now.

I find it interesting that you accuse someone who uses both C# and F#

It's not about _using_ both languages, it's about suggesting syntaxes so that the two look exactly the same.

using the . operator in some way would seem a more natural fit.

Using . operator for what? Forward pipe operator? That would be a real natural fit.

I maintain that there are two types of C# devs - C#3+ devs and those that stopped at C#2

I presume that you are from the second type of C# devs. So why bother.

without currying as a first class language feature, I'm not even sure how useful this feature would be anyway.

It's not "currying" this is just a syntactic sugar for postfix function application. And according to the above, this wouldn't be useful for you at all.

@eyalsk I apologize for my rude comments.

@paulomorgado please! I merely said why I don't like the pipe symbol, I don't expect anything from anyone and I certainly don't expect the C# team to design C# for my taste!

I made an honest comment on why I don't like the pipe symbol itself, nothing less, nothing more! did I commit some sort of a crime? and yes I was serious.

@gafter no problem.

@HaloFour

Those features/concepts come from those other languages, but the syntax was adapted to fit with C#. In this case you're just duplicating functionality already directly available in the language through F# syntax.

I agree 100%.

@alrz I don't know how you got the impression of what sort of developer I am. I actually use F# more than C# these days, but I can see where C# is trying to go.

Without currying you limit the ability to create arbitrary pipelines, as you can't compose as easily. Just like LINQ was built on several smaller language features - you remove any one of them and the overall package is nowhere near as compelling.

I, like others, are simply trying to offer advice based on what we've seen across several languages. This proposal - again IMHO, and irrespective of what operator you use - feels quite unnatural compared to idiomatic C#.

Up to you whether you want to take that on board or not. I've said my piece, and don't want this to turn into some flame war, so I'm going to disengage from the thread.

I also like this feature even though i'm not a huge fan of pipes (personal preference only) but i can adapt to have it in the language :)
just throwing it out there but maybe it could even a word like "to" or "into" or something.

Also, regarding beginners and such, i think we also need to remember that beginners usually don't have a lot of frame of reference from other languages, especially less mainstream ones. Therefore i think it might be a mistake to avoid constructs because they have different meanings in other languages.

Imo, if you know a lot of other languages already, you're probably have little difficulty learning the difference between those languages and c# simply because you've already got significant experience. Its learning your first language that is the real difficulty.

(tl;dr; i think we should be afraid to repurpose syntax from other languages if it fits)

@gafter Following @metatron-the-chronicler's idea #100 (Comment) that you were interested in,

How about making it totally transparent (using no operator!) in _some contexts_ like

Console.ReadLine()
File.ReadAllBytes()
SHA1.Create().ComputeHash()
BitConverter.ToString()
Console.WriteLine();

So, repeating some of production rules would be allowed.

@alrz I think that would be terribly confusing.

@gafter as long as it wouldn't be _ambiguous_, I don't think that it would be _confusing_. :smile:

@alrz I wouldn't want a single semicolon to change what my code does.

@orthoxerox It does today.

while(...); // !
NoWay();

@alrz and that's what Eric Lippert included in his list of things in C# that aren't that good.

@orthoxerox Man! this is not just C#, pretty much all C-family languages, it's the nature of _embedded-statement_. You can't avoid it — every single character counts and semicolon is no exception.

_If you're not good at something, make it worse._ kidding.

I think it would be better if extension methods were extended to support method groups, say via delegate conversions. Then this, and other related compositions, could be nicely implement as libraries.

I think the precedence should be lower than await operator, so you could write

var result = await foo.AsyncFoo() |> Baz();

Or (in a more special case),

var result = F() |> await Foo.BarAsync();

Unary operators (such as await) have high precedence and bind quite tightly.

unary-expression
        : primary-expression
        | '+' unary-expression
        | '-' unary-expression
        | '!' unary-expression
        | '~' unary-expression
        | pre-increment-expression
        | pre-decrement-expression
        | cast-expression
        | await-expression
        ;

I suggest this syntax :sunglasses:
It is not very fresh though..

C# var l = Console.ReadLine(); var b = File.ReadAllBytes(l); var h = SHA1.Create().ComputeHash(b); var s = BitConverter.ToString(h); Console.WriteLine(s);

@omariom That is only possible in a _statement context_. For example, the proposed pipe operator could be used in a base constructor invocation, but your technique could not.

@alrz

which, suprise, suprise, turn to heap allocation in async code, so don't use them so carelessly.

They're heap allocated regardless of whether or not they're assigned to a variable. If you're referring to the async method state machine then the use of any variables that cross an await boundary are promoted to fields within that state machine, but that doesn't result in additional heap allocations. This forward pipe operator will still have to squirrel away these values so that it can use them which it will accomplish by promoting them as field variables, just like their non-piped nested invocation version does.

@gafter

That is only possible in a statement context.

True

@HaloFour Apparently I wasn't sane when I wrote that.

+1 for this proposal. It's a great idea.

This is a great idea.
I agree that the syntax should be |>, and ?> for null forwarding.
I also think that in case of async methods being used in the chain, the following is more intuitive:

new StreamReader(path)
|> await StreamReader.ReadToEndAsync
|> Console.WriteLine

I have posted enhanced version #8670

With unified call syntax (which is proposed for C++) it could be even nicer:

Console.ReadLine()
.File.ReadAllBytes()
.SHA1.Create().ComputeHash()
.BitConverter.ToString()
.Console.WriteLine();

Basically dot instead of pipe.

@omariom And if Console.ReadLine() returns something that has a File property? That syntax definitely would be a breaking change.

And if Console.ReadLine() returns something that has a File property? That syntax definitely would be a breaking change.

homer-simpson-doh

@omariom Reading the draft spec for C++ it seems that they have similar concerns. The only non-breaking way would be to have the unified call considered only after exhausting all other possible overloads. Even then, that would break if someone added a completely unrelated overload to the class. But I guess C# has the same concern with extension methods and ambiguous matches. I'd be curious as to how they resolve the issues if that C++ proposal ever gets adopted into the spec.

I love this proposal, it looks like a small feature but it has the potential to greatly improve readability. Furthermore, the |> operator is IMHO the best option, especially as F# also uses it.

What if we want to pipe the result of expression A into a string interpolation, or method that should be provided with more arguments?

Maybe the standard should include something like this:
var f = Foo() |> $"{_} = {Bar(_, 5)}:F2";

Your thoughts?

@iDaN5x Forwarding is specifically useful for chaining methods, not arbitrary expressions or statements, though your example could be written like var f = $"{Foo() |> Bar()}:F2".

@alrz I understand your view of the string interpolation aspect, but what about the second case I described?
What if a function in the chain should receive extra arguments beside the value returned by last method in the chain?

@alrz As you can see in my second example in the opening post, it can be achieved by using named args, but I'm not sure if it will be considered, because it might get complicated or ambiguous.

And I don't think you understand what my example tried to accomplish... The string wouls interpolate to "something = 5.21"

The solution you described can work but it hurts the elegance of the code, which is the heart of this proposal.
If we could access the result of the Last operation by a named var like _, it would be nicer.

|> as the operator makes the most sense, I'd say, given that it's already recognizable from:

  • F# (where it behaves the same as mentioned here -- this could also mean a unification in how the two behave)
  • OCaml
  • Elixir
  • Scalaz
  • Elm

And it has enough mindshare that there have been efforts to reproduce it in various languages (a quick google will show results for people trying to recreate it in Swift, mainline-Scala, Haskell, ES7 and various other languages). The phrase "forward pipe operator" and |> are by now nearly synonymous; indeed, with | being "pipe", |> can be read as "pipe forward" (or "forward pipe").

To use a "forward pipe" operator that not only is unfamiliar, but also conveys neither "pipe" nor "forward" would just be unnecessarily confusing.

I have implemented |> in https://github.com/orthoxerox/roslyn/tree/features/forwardPipe

It has no advanced features of #3171, code formatter hasn't been touched yet and I have a suspicion I have to fix the background compiler's lexer separately, but it works.

@orthoxerox I don't see any tests in which there are additional arguments on the right-hand-side, e.g. expression |> M(arg)

Also, you seem to have allowed (required?) the omission of the parens on the right-hand-side.

@gafter Have I? I wanted |> to accept literally any expressions on both sides and turn itself into an invocation at the binding stage, allowing later stages to weed out invalid calls. I guess I'll try and add more tests to see where I failed. I'll have to fix my build process first, though.

@orthoxerox I suggest trying some examples from the original issue, _as written_, to see if we're all on the same page.

@gafter Okay, the following examples will not work by design, because I plan to implement @-substitution to make this explicit.

We can take a step further and specify named args (similar to currying proposed in #3171):

And as mentioned in #5206 and discussed in #347 we can use tuples to support methods with multiple parameters:

My code also doesn't accept trailing () on the right hand side, but is it a good idea to accept it? If I have two overloads of Foo with types string -> string and void -> string -> string, which one is actually called when I write "hello" |> Foo()? Right now it's always the latter, with trailing () allowed it becomes ambiguous.

@orthoxerox It is exactly the same as Foo("hello"), how is that ambiguous?

@orthoxerox Oh I see, you are forwarding to the method group and/or delegate on the rhs, in that case you can't support currying because once you insert arguments, it won't be a method group anymore, also, conditional access doesn't support method groups but you should be able to forward to it, e.g.

Action<object> a = foo?.Foo; // ERROR

obj |> foo?.Foo();
// equivalent to
foo?.Foo(obj);

@alrz hm, I'll see what I can do about conditional access.

@alrz in that case, the way that the langs I listed implement |> is that Foo("hello") would be equivalent to "hello" |> Foo, not "hello" |> Foo().

Rather, "hello" |> Foo() would be equivalent to Foo()("hello").

If C# is going for "principle of least surprise" with it's forward pipe operator, that's how I would recommend implementing it.

@spencerwi C# doesn't have built-in currying support so right-hand-side is always special case. You can't forward to rhs as an _expression_ i.e. method group or delegate.

"hello" |> Foo(arg);
// should be translated to
Foo(arg, "hello");
// not
Foo(arg)("hello");

F# doesn't support currying for CLR methods e.g.

"hello" |> Console.WriteLine // OK
"hello" |> Console.WriteLine "v: {0}" // ERROR

So that would be the expected behavior in C#.

This is the syntax I'm imagining.

_relational-expression:_
 _forward-expression_

_statement-expression:_
 _forward-expression_

_forward-expression:_
 _relational-expression_ |> _forward-target_
 _relational-expression_ ?> _forward-target_

_await-forward-target_:
await _invocation-expression_
await _await-forward-target_

_forward-target:_
 _object-creation-expression_
 _invocation-expression_
 _await-forward-target_

Ah, true. The lack of automatic currying does make things more difficult.

The dirty deed of arg |> obj?.Foo support is done, but I have a few worries.

  1. I did it by building a custom AST in the Binder method for the pipe. Is that an acceptable implementation?
  2. For some reason the whole expression is not recognized as a conditional method call when I hover over the |> in VS, even when I conditionally call a delegate.

EDIT: no, unfortunately it's not done. My code fails on (why don't iPads have backticks?) foo?.bar.Baz, because of the precedence rules. Back to the drawing board.

@orthoxerox This is how you type a backtick in iOS.

I've added some details if anyone's interested. Just some suggestions regarding overload resolution.

/cc @orthoxerox

+1 for this proposal. And I'm with |> and ?> syntax.

Using some form of the existing invocation syntax . was alluded to a couple of times above. One option would be to define a syntax A.(B), where B is anything you could assign to an appropriate delegate. So B could be a method group or a lambda, for example.

You can then write something such as this:

Console.ReadLine()
    .(File.ReadAllBytes)
    .(SHA1.Create().ComputeHash)
    .(BitConverter.ToString)
    .(hash => Console.WriteLine("The hash is {0}", hash));

This integrates the familiar syntax for invocation with the familiar syntax for constructing delegates, though in this case I assume the compiler would be rewriting the expression rather than constructing and calling delegate objects. ?. would of course work similarly.

From a readability point of view, I guess the parentheses aren't ideal - I'd be interested in views on that. On the other hand, it feels more "C#-ish" than adding a completely new operator.

@christopherpaul this seems interesting! but yeah the suggested operator is more readable. :)

OK, I finally found some free time to finish the first part. Unfortunately, I didn't like @alrz's design, so this is what works now:

  • arg |> Foo, equivalent to Foo(arg)
  • arg |> Foo?.Bar?.Baz, equivalent to Foo?.Bar?.Baz(arg)
  • arg |> new Foo, equivalent to new Foo(arg)

Planned:

  • arg ?> Foo
  • arg |> Foo(otherArg, @), equivalent to Foo(otherArg, arg)

A question to the readers: how important is @alrz's proposed evaluation order to you? If you write PrintFoo() |> FooBar(PrintBar(), @), will you demand to see "Foo" printed first and not "Bar"?

Or, should PrintBar()(PrintFoo()) and PrintFoo() |> PrintBar() produce side effects in the same or different order?

    static Action<int> PrintBar()
    {
        WriteLine("Bar");
        return (i) => WriteLine("Done");
    }

    static int PrintFoo()
    {
        WriteLine("Foo");
        return 0;
    }

@orthoxerox yeah, I'd expect to see "Foo" printed first, meaning, execute PrintFoo() first, PrintBar() second and lastly FooBar(...).

p.s. Good job. :)

Forward pipe operator is something I would definitely be glad to see in the language. I keep finding my C# code looking more and more functional these days, and the lack of the forward pipe is starting to hurt.

However, I'm not a fan of the way the original proposal by @alrz tries to deduce which RHS argument the LHS result should be passed to. There are three main reasons for this:

  1. It doesn't allow things like GetSomeTuple() |> Log( @.Category, @.Items.Count ). Basically anything that needs to access LHS result more than once is off the table and would require writing an additional method.
  2. It also doesn't allow piping to anything except the method calls, so things like string interpolation or @throw expressions (if we ever get them) in general are off the table as well.
  3. Piping to the method with multiple parameters just looks too verbose if you have to specify the names of all parameters excepts the one that's being piped.

I think a way to explicitly reference the LHS value is a better way to do this. @orthoxerox's suggestion of using @ variable could work, but I think it may be appropriate to reuse the lambda syntax here:

void LogSomething()
   => GetSomeTuple()
   |> tuple => $"[{tuple.Category}] {tuple.Items.Count}"
   |> text => Log( LogLevel.Debug, text );

The main benefit would probably be the way to combine pipe with tuple deconstruction:

void LogSomething()
   => GetSomeTuple()
   |> ( category, items ) => $"[{category}] {items.Count}"
   |> text => Log( LogLevel.Debug, text );

But this syntax would also allow the use of meaningful names for the piped arguments (which would be nice for the code readability if you have a bunch of pipes in a single expression) and I think it looks more familiar and C#-like than a magic @ variable.

I don't see why you need extensions to the language for this when it can be implemented quite easily in a library. Consider the following simple extension methods:

public static class DelegateExtensions
{
    public static Func<TOut> Compose<TMid, TOut>(this Func<TMid> f, Func<TMid, TOut> g) => () => g(f());
    public static Action Compose<TMid>(this Func<TMid> f, Action<TMid> g) => () => g(f());

    public static Func<TIn, TOut> Compose<TIn, TMid, TOut>(this Func<TIn, TMid> f, Func<TMid, TOut> g) => arg => g(f(arg));
    public static Action<TIn> Compose<TIn, TMid>(this Func<TIn, TMid> f, Action<TMid> g) => arg => g(f(arg));
}

Now you can write:

Func<string> f = Console.ReadLine;
var program = f
    .Compose(File.ReadAllBytes)
    .Compose(SHA1.Create().ComputeHash)
    .Compose(BitConverter.ToString)
    .Compose(Console.WriteLine);

program(); 

// Input: consoleapplication1.exe
// Output: FB-EF-53-C8-CE-B4-7D-63-19-BB-E1-3F-22-89-02-CB-1F-0C-00-CA

The only thing that could make this a bit nicer is delegate type inference to avoid the explicit Func<string> declaration at the top, but I use the language-ext library for little conveniences like the following:

var program = fun(Console.ReadLine)
    .Compose(File.ReadAllBytes)
    .Compose(SHA1.Create().ComputeHash)
    .Compose(BitConverter.ToString)
    .Compose(Console.WriteLine);

program(); 

// Input: consoleapplication1.exe
// Output: FB-EF-53-C8-CE-B4-7D-63-19-BB-E1-3F-22-89-02-CB-1F-0C-00-CA

You can of course write your own simple implementation of fun if you don't want the library.

Somewhere in this thread I think I saw someone discussing how to include functions of multiple arguments in this pipeline. Once again, use standard functional composition tools like currying and partial function application here rather than inventing new operators (both are easily implemented in C#, although could be made nicer with variadics).

I wish C# allowed custom operators so I could invoke Compose with some nice little character sequence, but that is a topic for a separate issue.

As a side note, once extension everything lands, you could e.g. use the & operator instead of Compose, which reads rather nicely:

var program = fun(Console.ReadLine) 
    & File.ReadAllBytes 
    & SHA1.Create().ComputeHash
    & BitConverter.ToString
    & Console.WriteLine;

program(); 

// Input: consoleapplication1.exe
// Output: FB-EF-53-C8-CE-B4-7D-63-19-BB-E1-3F-22-89-02-CB-1F-0C-00-CA

@masaeedu There are at least two problems with this approach though, isn't?

1) People need to bloat their code with a library although this is probably solvable by adding it to the framework.

2) Performance - You need to use delegates and make an extra function call for each function you pass whereas when we have this in the language the compiler can generate the messy and ugly code for us without having these redundant calls.

@eyalsk Better bloat your code than bloat the compiler. Not everyone needs this, and adding a nuget package doesn't really bloat anything besides your project.json.

Regarding performance: you can accept Expression<Func<...>> and compile whatever you want instead of accepting Func<...> and composing with a wrapping lambda as I am. Any performance optimizations you could envision within the compiler (e.g. composed function inlining) can be achieved by a library author with sufficient expertise.

@masaeedu "Better bloat your code than bloat the compiler." -- your opinion! and I respect it but I strongly disagree, the language needs to help me express logic and if I want to use a functional paradigm to do it I don't need to download a library or anything to do so, I expect it to have this baked in.

What I really want to see in a future version of C# is a feature that will allow us to select one or more programming paradigm profile where you use only what you need, this will help solve these issues where the more the language gets _richer_ the compiler gets bloated but in practice, again, the language needs to allow me to express things in natural ways and the compiler needs to adheres to that.

Instead of downloading libraries, downloading official compiler extensions such as "C# Functional Paradigm", "C# Async Paradigm" where at its core there's "OO Paradigm".

I don't have issues with downloading 3rd-parties libraries but the biggest issue for me is maintenance and support, people write libraries, abandon them and I either need to maintain them myself or find another.

I already got bitten by this multiple times but no more!

I don't have issues with downloading 3rd-parties libraries but the biggest issue for me is maintenance and support, people write libraries, abandon them and I either need to maintain them myself or find another.

Maintenance of libraries is orthogonal to language design concerns. You could request this as a BCL feature or a Microsoft.Extensions.Functional spinoff if you are concerned that no one but Microsoft would maintain the library properly, but sticking a feature into into the language with the rationale that Microsoft will be forced to maintain the underlying implementation is a poor justification IMO. Given the amount of activity on issues such as these, I have very little doubt that there is enough demand and talent for functional C# to maintain such a library.

IMHO the language should provide powerful primitives that compose well in the hands of the developer, rather than continually pushing back work on these to provide narrowly scoped features. In C#7 we've already ended up with somewhat hamstrung versions of pattern matching and declaration expressions. On their own they solve a single problem well, but they compose poorly with each other, and with other functional programming concepts.

Similarly, the proposed |> operator composes poorly with other functional programming concepts without additional revisions to the language. Lets say we want to pipe to/from functions of multiple arguments. We realize this necessitates currying and partial function application. Now we must decide between:

  • filing issues asking for currying, PFA language features that interoperate with our piping feature correctly and performantly. Expect lots of work, contortions around backward compatibility, and a delay in other features that people want. All for some syntax sugar
  • baking partial application into the |> syntax, with the disadvantage that you can't use these broadly applicable concepts elsewhere
  • writing or importing code that implements these concepts with standard C#, which means we're back to square one, but with the handicap that we may no longer hook into the forward piping implementation as easily for performance optimizations

From my perspective it would be better to e.g. ask for custom infix operators or extension operators than to ask for |>, since the former would compose with existing language features to enable the scenario you're describing, _in addition to_ enabling a number of other frequently requested use cases.

YMMV.

@masaeedu

Maintenance of libraries is orthogonal to language design concerns.

Indeed, they are but I was speaking about having this feature as part of the language vs using a library, I didn't say that this is THE reason we should add it to the language.

In C#7 we've already ended up with somewhat hamstrung versions of pattern matching and declaration expressions.

Can you give an example to that? I mean what exactly do you mean? I know it's not as complete as you'd expect in other functional languages but iirc they stated somewhere that work on both of these things is still going after C# 7.

Similarly, the proposed |> operator composes poorly with other functional programming concepts without additional revisions to the language.

Well, I agree that this can lead to more issues and it's likely that it will open a new can of worms but personally, I really like the succinct syntax over a function call because to me and this is purely subjective, it reads better, even though I had different ideas about the symbol itself, however, like you said adding a feature that will allow us to add a _custom operator_ can help tremendously to have the best of both worlds, however, this can also open a new can of worms so really I'm torn on this.

I'm not sure whether it was proposed before but maybe you can write a proposal about this _custom operator_, it seems pretty interesting. :)

@eyalsk,

The syntax for such custom pipe operators could be something like:

public static TR new operator "|>"<T, TR>(T value, Func<T, TR> func) => func(value);
public static void new operator "|>"<T>(T value, Action<T> action) => action(value);
public static TR new operator "||>"<T1, T2, TR>((T1 p1, T2 p2), Func<T1, T2, TR> func) => func(p1, p2);
public static void new operator "||>"<T1, T2>((T1 p1, T2 p2), Action<T1, T2> action) => action(p1, p2);
....

And for a case of:

string F(int p1, string p2) => ...
string G(string p1, string p2) => ...
int H(int p) => ...
int I(int p) => ...

The following would then be equivalent:

G(F(1, "2"), "3");
((1, "2") ||> F, "3") ||> G;
H(I(1));
1 |> I |> H;

What I really want to see in a future version of C# is a feature that will allow us to select one or more programming paradigm profile where you use only what you need [...]. Instead of downloading libraries, downloading official compiler extensions such as "C# Functional Paradigm", "C# Async Paradigm" where at its core there's "OO Paradigm".

So you want #13322?

@Joe4evr exactly but I wouldn't go as far as externalizing "yield return". :)

@Joe4evr, @eyalsk,

The problem I see there is that 'at its core there's "OO Paradigm"'. :smile:

Unless there were a way of having the core C# compiler be composed of very few keywords, and almost no assumptions of behaviour, so that I could for example load in the functional extension and have immutable-by-default, unit, rather than void etc, then such a modular approach will simply sideline functional and async features even more than they are at present.

I agree. Not to mention that supporting such a "modular compiler" model in the first place would _very probably_ require another major rewrite of the compilers and IDE integration and all the other stuff that comes with it.

@DavidArno Well, yeah, it might be a problem but I don't work on the compiler to actually know how difficult is to refactor the different components to make this so I just stated an opinion of how I think it should be composed. :D

@Joe4evr Maybe, I don't _really_ know but it seems really, really odd to me that they need to rewrite everything from the ground up, what's the point of engineering a software system that can't change? systems shouldn't be rigid.

This is just my point of view but from where I'm coming from modularity is a feature like any other, you need to introduce it when there's a need for it so saying that they need to rewrite everything is really unlikely but refactoring is more probable.

The very concept of modularizing C# into a series of pluggable dialects is an awful one. The nature of the support surface for such a beast would be monstrous, to be generous. The CLR was designed to be mostly language agnostic and there are numerous languages that already offer support for these kinds of features in a way that is internally consistent. I'd suggest using them.

Perhaps one area of improvement would be in polyglot solutions. If it would be easier to comingle F# and C# then some of these concerns I think would go away. But to try to morph C# into F# while keeping it C# or having it be C# or F# based on some project or file settings is just ludicrous.

@HaloFour Yes but hypothetically say that we had these foundations, do you think it would improve things? or it would just make things redundant and complicated for the consumer that is for us?

@eyalsk

Yes but hypothetically say that we had these foundations, do you think it would improve things?

I don't think such "foundations" are remotely possible in a programming language. Could you imagine the design process that would be required in order to ensure that syntax changes in one dialect doesn't completely break syntax in another dialect? It's tricky and time consuming enough when there isn't a separate Cartesian product of external possibilities to worry about.

As for a consumer, could you imagine trying to consume two different "C#" libraries that were each designed to be used from entirely different paradigms? I think it'd be a nightmare. It's bad enough already having to bridge the gap from Scala or F# libraries that were clearly not designed to be used from Java or C# programs.

@HaloFour Okay, yeah, you're right. :smile:

I see that that this proposal (along with a bunch of other interesting ones) are marked with the label "2 - Ready". Is that an indication of there being a finished specification of the feature somewhere that you can have a look at? Or is a specification one of the products of the implementation phase (as implied by the label "3 - Working" )?

(I had a look at: https://github.com/dotnet/roslyn/wiki/Labels-used-for-issues, but I found nothing satisfying my curiosity.)

@niklaskallander It means "ready to prototype" per Developing a Language Feature.md.

Was this page helpful?
0 / 5 - 0 ratings