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

Add a function pipe operator #1246

Open
DartBot opened this issue Feb 17, 2014 · 33 comments
Open

Add a function pipe operator #1246

DartBot opened this issue Feb 17, 2014 · 33 comments
Labels
request Requests to resolve a particular developer problem

Comments

@DartBot
Copy link

DartBot commented Feb 17, 2014

This issue was originally filed by @chalin


Both AngularDart and Polymer have special syntax in support of function pipes (for Filters and Transformers) through the pipe | (bar) operator. E.g.

    person.lastName | uppercase

It would seem a natural fit for Dart to have native support for such a pipe operator. A case for the addition of a pipe operator in Dart is given here: http://pchalin.blogspot.com/2014/02/case-for-pipe-operator-in-dart.html

As is mentioned in the blog entry, I believe that added support should be considered for function composition operators (forward and/or reverse) as well.

@DartBot
Copy link
Author

DartBot commented Feb 17, 2014

This comment was originally written by @chalin


It just occurred to me that Dart already supports operator definitions and that the bar (|) operator is among the supported operator symbols. I suppose then, that all we need is for the dart:core Function class to be enhanced with a bar operator definition.

@kevmoo
Copy link
Member

kevmoo commented Feb 17, 2014

Removed Type-Defect label.
Added Type-Enhancement, Area-Language, Triaged labels.

@DartBot
Copy link
Author

DartBot commented Feb 18, 2014

This comment was originally written by @zoechi


This is a nice idea but | is already in use for bitwise OR.

@DartBot
Copy link
Author

DartBot commented Feb 18, 2014

This comment was originally written by @chalin


Re #­1. On second thought, adding a bar operator to the Function class would not work since in an expression like x | F the receiver is x not F and hence bar would have to be declared for x. Thus to support this as a method, it would need to be added to Object, ... or given some special support by the compiler.

@DartBot
Copy link
Author

DartBot commented Feb 18, 2014

This comment was originally written by @chalin


Re #­3. There is no fixed prescriptive use of operators in Dart. I.e., Dart supports operator overloading see [1], Table 2.11. Operators that can be overridden. Notice that the bar operator is part of the list.

[1] https://www.dartlang.org/docs/dart-up-and-running/contents/ch02.html#classes-operators

@DartBot
Copy link
Author

DartBot commented Apr 28, 2014

This comment was originally written by @vicb


see https://code.google.com/p/dart/issues/detail?id=18485

@kevmoo
Copy link
Member

kevmoo commented Apr 28, 2014

Issue dart-lang/sdk#18485 has been merged into this issue.


cc @gbracha.

@kevmoo
Copy link
Member

kevmoo commented Apr 28, 2014

Here's a vote for the F#-like "|>" syntax. We avoid overloading the behavior of bit-wise.


Set owner to @gbracha.

@gbracha
Copy link

gbracha commented Apr 29, 2014

There are useful details in the duplicate issue dart-lang/sdk#18485. Something along these lines is definitely possible, but it will take time - not because it is hard, but because we now have a standards process.


Added Accepted label.

@lrhn
Copy link
Member

lrhn commented Apr 29, 2014

Issue dart-lang/sdk#18516 has been merged into this issue.


cc @floitschG.

@DartBot
Copy link
Author

DartBot commented Apr 29, 2014

This comment was originally written by @chalin


Again, thanks for promoting function pipes Kevin. I agree that new syntax for piping makes more sense; I had originally proposed use of | because it matched what Angular and Polymer use.

but it will take time ... because we now have a standards process.

In that case, I would hope that the added support of function composition (mentioned at the end of the original entry for this issue) will be considered at the same time. Should I open a separate issue to track this?

@kevmoo
Copy link
Member

kevmoo commented Apr 29, 2014

RE #­11 from pchalin:

Please open a separate issue. We could offer method piping without providing a general model for method composition.

@DartBot
Copy link
Author

DartBot commented Apr 29, 2014

This comment was originally written by @chalin


Doen: https://code.google.com/p/dart/issues/detail?id=18522

@yjbanov
Copy link

yjbanov commented Aug 22, 2015

Has anyone started or is anyone interested in starting a DEP for this?

@chalin
Copy link

chalin commented Mar 13, 2018

FYI: A proposal for adding the simple-but-useful pipeline operator to JavaScript.. /cc @munificent @kevmoo @kwalrath

@kevmoo
Copy link
Member

kevmoo commented Mar 13, 2018

@chalin – you know I'm a fan!

@yjbanov
Copy link

yjbanov commented Mar 19, 2018

I think Dart should take advantage of the static type system. Could, for example, extension methods satisfy most of the pipeline operator use-cases, without the additional call syntax?

@sclee15
Copy link

sclee15 commented Apr 15, 2018

I love |> in Elixir! I miss the pipe operator alot in Dart

@darting
Copy link

darting commented Jul 25, 2019

Hey, any news?

@munificent
Copy link
Member

No news. We've been very focused on non-nullable types and extension methods, so haven't had time for many other small-scale language changes.

@aislanmaia
Copy link

I love |> in Elixir! I miss the pipe operator alot in Dart

This is perfect and used in another languages as well, like Elm, etc.

@kevmoo
Copy link
Member

kevmoo commented Sep 30, 2020

This seems redundant to extension methods – https://dart.dev/guides/language/extension-methods

I'm tempted to close this as ~fixed.

@munificent @leafpetersen ?

@kevmoo kevmoo transferred this issue from dart-lang/sdk Sep 30, 2020
@mateusfccp
Copy link
Contributor

@kevmoo
How is this redundant to extension methods?

@lrhn
Copy link
Member

lrhn commented Sep 30, 2020

You can't get proper typing with extension operators.
If you define it as:

extension PipeExt<T> on T {
   operator |(Function(T) f) => f(this);
}

you can't get the return type properly typed. If operators could be generic, it could work, but they can't, so it won't.

Also doesn't work for a first operand which already declares a | operator.

You definitely can't do something variadic like:

int Function(int, String, bool) f = ...;
42 |> f("a", false);

which I'd also like.

@kasperpeulen
Copy link

I thought kevmoo meant that extension method serve the same purpose of the pipe operator.
And not that you can implement a pipe operator using extension method (would be cool if you can!).

I agree with that. Extension methods are best seen as just another syntax for a top level function (or static function).
And the extensoin metthod syntax allows those functions to be composed much better than top level functions.

Top level functions only compose if they are one to one. In functional programming languages, this usually solved by automatic currying them and having the last argument as the "data" argument.

So you won't see the top level functions as you normally see in Dart, but it will curried and data last, so for example:

double Function(double) Function(String, bool) f = ...
double Function(double) Function(int) power = ...
double Function(double x) Function(double a, double b, double c) polynomial = ...

This allows function composition, without a composition operator it would look like this:

var x = polynomial(1,2,3)(power(5)(cos(sin(f('a', false)(42)))));

But most of those languages ship a composition operator, and it would simplify to something like this:

var x = (polynomial(1,2,3) * power(5) * cos * sin * f('a', false))(42);

Now we still have the problem that mathematics makes this huge syntactical mistake, and that is that it does function composition in the wrong order! That is the purpose in my opinion of the pipeline operator, to compose functions in a left to right (chronological) order. So you can write:

var x = 42 |>  f('a', false) |> sin | cos | power(5) |> polynomial(1,2,3);

While with extension method, you do the same, just with a dififerent syntax:

var x = 42.f('a', false).sin().cos().power(5).polynomial(1,2,3);

This probably makes more sense in an OOP inspired language as Dart (similar as Kotlin and Swift) while the pipe operator makes more sense in an FP language such as F#, OCaml, Elixir, Elm.

@lrhn
Copy link
Member

lrhn commented Oct 2, 2020

You can definitely do something similar to a function pipe by writing sufficiently many extension methods.
It doesn't easily allow you to simply pipe into an existing top-level function, like value = value |> sin() |> sqrt();
You'd have to first write an extension like

extension X on double {
  double sin() => math.sin(this);
  double sqrt() => math.sqrt(this);
}

before you can just write value = value.sin().sqrt();. (At that point, it does look better, but I never liked |> and would prefer value = value->sin()->sqrt(); to begin with.)

@lrhn
Copy link
Member

lrhn commented Oct 23, 2020

It's a good point that having an easy currying syntax makes it less important to include it in the pipe itself.
So, instead of:

expr->foo(2);  // meaning foo(expr, 2), and no option to change that.

you can write

expr->(=>foo(_, 2))

(Using syntax from #8).

Or we can just say that expr->expr(args) is implicitly expr->_=>expr(args) and allow a _ directly, so its just expr->foo(_,2). Might be a little too syntax specific, though. (See #8 for why we can't just make foo(_, 2) a function with no syntactic way to see where the function should be introduced - otherwise it could just be foo((_)=>_, 2)).

@lrhn lrhn added the request Requests to resolve a particular developer problem label Oct 23, 2020
@NANASHI0X74
Copy link

NANASHI0X74 commented Feb 10, 2021

I'm a big fan of this, I want to add another consideration: Maybe the pipeline operator should also be able to pipe through multiple arguments, e.g. like

var func = (x, y) =>x+y;
a, b |> func;

This would especially be useful together with #68

also maybe it would be nice if this also worked with the proposed tuples/"records" https://github.com/dart-lang/language/blob/master/working/0546-patterns/records-feature-specification.md

@venkatd
Copy link

venkatd commented May 9, 2021

I really like the idea of a pipe operator. It allows me to cleanly decompose my code into regular functions which I find is the simplest possible unit.

Unfortunately, breaking something apart into functions today requires declaring non-final variables, conditionals along the way, and so on. An example of this might be the bulid method for the Container in Flutter. We've got some sort of a pipeline there, but it's implemented with if statements and a mutable variable.

think extension methods works well for general-purpose extensions like Iterable. But if I've got some implementation details and I want to run a bunch of transformations over some data, the pipe operator reduces the friction for composition. I rarely reach for extension methods if there's no reuse because of the extra layer of indirection.

@lrhn
Copy link
Member

lrhn commented Oct 18, 2021

I'm actually starting to lean towards "pipe" being an unsatisfactory primitive, one where every use-case has a better alternative.

For doing function calls with the receiver as implicit argument, we have to choose where to put that argument once and for all, because it's implicit. Doing someExpression |> print looks nice, but doing someExpression |> compare(a) doesn't work very well because it's unclear whether we the value of someExpression becomes a first or second argument (if it works at all, and it really should).

So, maybe we should make it an implicitly bound variable named, say, it instead: someExpression |> compare(a, it) or someExpression |> compare(it, a). Nicer. And someExpression |> print(it) is stil nice enough.

The problem with implicit names is that they may conflict, and then you want to have an override. Example:

  someExpression |> someOtheExpression |> compare(it, it2)  // ???

(If there is already a local variable named it in scope, should we rename the second one to be introduced?).

And then we are back to implicit bindings, so why not just introduce explicit bindings, like:

 let it = someExpression in let it2 = someOtherExpressiion in compare(it, it2)

It's effectively the same, just with explicit naming of the variables. We can borrow an idea from #1201 and allow implicit naming like:

 let foo.bar.value in 
 let foo.bar.otherValue in 
     compare(value, otherValue)

That's a fully generalized local variable introducing expression, which we would want for a number of of other reasons (#1201, #1420, and several other similar ideas).
As a feature, I think that beats pipes by being more general and solving the same problems, even if it might be slightly more verbose.

Also, pipe is not essential to having statements inside expressions. You can already do () { .... ; return value; }().
That doesn't easily go at the end of a chain, but that's a solvable problem.

You can do pipe today as:

extension Pipe<T> on T {
  // I'd call it `do`, but that's a reserved word.
  R pipe<R>(R Function(T) transform) => transform(this);
}

and do someExpression.pipe((v) { ... whatever ...; return value; }), so the power introduced by a language-level operator is minimal. It's only syntax.
(Sadly can't use operators, since they can't be generic.)

Using the pipe syntax doesn't seem like it adds that much value. Even without that extension, you can write chain|> (v) {...;return ...;} as () { var v = chain;...;return ...}() or, if we get any kind of expression variable binding, as something equivalent to let v = chain in () { ...; return ...; }().

(Or we can introduce a statement expression, like #1211 or something more general like #132, if we really want to support statements inside expressions. That would work with variable binding features too.)

So, all in all, I think the "pipe" feature is too narrow and a general variable binding feature would be better (more powerful and more general) and would still solve the same problems.

@svarlet
Copy link

svarlet commented Oct 18, 2021

It sounds like you are trying to build perfection for an otherwise simple demand: to enable writing sequential expressions concisely and without unnecessary intermediate bindings. You offer sound arguments but perhaps to a different problem, and miss that the ideal solution is perhaps neither what is demanded or your alternative but to offer both to the programmers.

On the position of the value of an expression in the downstream expression: developers seem happy with an arbitrary-and-always position. In Elixir, for example, the computed value becomes the first argument of the next function call. So expr |> f(a) is equivalent to f(expr, a). In Elm (and I think Haskell too), they become the last argument of the following expression which plays nice with partial function application. Anyway, that is to say that adopting an arbitrary and unconditional stance works. There's no "but where is the value ending here?", the language is design "this way" so there's no doubt. Do you see a particular reason why Dart could not or should not follow any of these examples?

Similarly, on the clashing of implicit names, the languages I mentioned don't have implicit names at all. You may feel it's too limiting, yet many developers are happy with this contract. When they do need to name intermediate values, they declare variables or use the let ... in ... syntax that you also suggest.

Nevertheless, considering that these developers have access to both options, you may assume they never use the pipe operator because the let ... in ... style is superior. That's not the case however. How do you reconcile that tension? Both styles are used because every context is different, and often the pipe operator is good enough and reads very well.

@yarn-rp
Copy link

yarn-rp commented May 19, 2022

You can definitely do something similar to a function pipe by writing sufficiently many extension methods. It doesn't easily allow you to simply pipe into an existing top-level function, like value = value |> sin() |> sqrt(); You'd have to first write an extension like

extension X on double {
  double sin() => math.sin(this);
  double sqrt() => math.sqrt(this);
}

before you can just write value = value.sin().sqrt();. (At that point, it does look better, but I never liked |> and would prefer value = value->sin()->sqrt(); to begin with.)

This isn't similar at all. I don't see any dev using pipe this way, because every new function needs to be declared inside the extension. The reserved word way isn't an ideal solution neither. Basically seems like your trying to give a more complex solution, that will fix in more scenarios, "but no one actually asked for it". Even that the let in approach is much more interesting, the pipe is simply perfect for writing sequential expressions, which is what we are asking in here

@ken-okabe
Copy link

ken-okabe commented Jun 20, 2022

. Doing someExpression |> print looks nice, but doing someExpression |> compare(a) doesn't work very well

This totally depends on the definition of the function compare, and in this case, compare(a) should be/return someFunction.

If you define plus function properly, the syntax would go

1 |> plus(2) |> plus(3) and so on.

Similarly,

final numbers = [1, 5, 10, 100];
final result1 = numbers.map((num) => num * 3);

will be

final result1 = numbers |> map((num) => num * 3);

If you define map properly.

Actually, during this thread, I have seen multiple comments by whom perhaps never used "pipe-operator" in real life.

Function/pipe operator x |> f is simply a binary operator of a function application that is f(x), and it's a famous and established operator in F# etc. There is nothing that "doesn't work very well " or concern or something to discuss, and please don't make this simple operator messy/complicated to have multiple arguments etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests