language icon indicating copy to clipboard operation
language copied to clipboard

Add a function pipe operator

Open DartBot opened this issue 11 years ago • 40 comments

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 avatar Feb 17 '14 21:02 DartBot

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.

DartBot avatar Feb 17 '14 21:02 DartBot

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

kevmoo avatar Feb 17 '14 23:02 kevmoo

This comment was originally written by @zoechi


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

DartBot avatar Feb 18 '14 06:02 DartBot

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 avatar Feb 18 '14 12:02 DartBot

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 avatar Feb 18 '14 13:02 DartBot

This comment was originally written by @vicb


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

DartBot avatar Apr 28 '14 16:04 DartBot

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


cc @gbracha.

kevmoo avatar Apr 28 '14 21:04 kevmoo

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


Set owner to @gbracha.

kevmoo avatar Apr 28 '14 21:04 kevmoo

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.

gbracha avatar Apr 29 '14 06:04 gbracha

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


cc @floitschG.

lrhn avatar Apr 29 '14 13:04 lrhn

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?

DartBot avatar Apr 29 '14 13:04 DartBot

RE #­11 from pchalin:

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

kevmoo avatar Apr 29 '14 13:04 kevmoo

This comment was originally written by @chalin


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

DartBot avatar Apr 29 '14 13:04 DartBot

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

yjbanov avatar Aug 22 '15 22:08 yjbanov

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

chalin avatar Mar 13 '18 11:03 chalin

@chalin – you know I'm a fan!

kevmoo avatar Mar 13 '18 16:03 kevmoo

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?

yjbanov avatar Mar 19 '18 05:03 yjbanov

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

sclee15 avatar Apr 15 '18 13:04 sclee15

Hey, any news?

darting avatar Jul 25 '19 03:07 darting

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.

munificent avatar Jul 25 '19 17:07 munificent

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.

aislanmaia avatar Sep 30 '20 18:09 aislanmaia

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

I'm tempted to close this as ~fixed.

@munificent @leafpetersen ?

kevmoo avatar Sep 30 '20 18:09 kevmoo

@kevmoo How is this redundant to extension methods?

mateusfccp avatar Sep 30 '20 20:09 mateusfccp

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.

lrhn avatar Sep 30 '20 20:09 lrhn

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.

kasperpeulen avatar Oct 02 '20 13:10 kasperpeulen

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 avatar Oct 02 '20 19:10 lrhn

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 avatar Oct 23 '20 10:10 lrhn

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

NANASHI0X74 avatar Feb 10 '21 19:02 NANASHI0X74

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.

venkatd avatar May 09 '21 21:05 venkatd

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.

lrhn avatar Oct 18 '21 12:10 lrhn