Coder Social home page Coder Social logo

Add a function pipe operator about language HOT 33 OPEN

dart-lang avatar dart-lang commented on May 28, 2024 82
Add a function pipe operator

from language.

Comments (33)

kevmoo avatar kevmoo commented on May 28, 2024 27

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


Set owner to @gbracha.

from language.

sclee15 avatar sclee15 commented on May 28, 2024 21

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

from language.

ken-okabe avatar ken-okabe commented on May 28, 2024 9

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

from language.

chalin avatar chalin commented on May 28, 2024 5

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

from language.

yjbanov avatar yjbanov commented on May 28, 2024 5

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?

from language.

svarlet avatar svarlet commented on May 28, 2024 5

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.

from language.

kevmoo avatar kevmoo commented on May 28, 2024 4

@chalin – you know I'm a fan!

from language.

kasperpeulen avatar kasperpeulen commented on May 28, 2024 3

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.

from language.

lrhn avatar lrhn commented on May 28, 2024 2

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.

from language.

lrhn avatar lrhn commented on May 28, 2024 2

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

from language.

NANASHI0X74 avatar NANASHI0X74 commented on May 28, 2024 2

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

from language.

lrhn avatar lrhn commented on May 28, 2024 2

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.

from language.

aislanmaia avatar aislanmaia commented on May 28, 2024 1

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.

from language.

DartBot avatar DartBot commented on May 28, 2024

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.

from language.

kevmoo avatar kevmoo commented on May 28, 2024

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

from language.

DartBot avatar DartBot commented on May 28, 2024

This comment was originally written by @zoechi


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

from language.

DartBot avatar DartBot commented on May 28, 2024

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.

from language.

DartBot avatar DartBot commented on May 28, 2024

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

from language.

DartBot avatar DartBot commented on May 28, 2024

This comment was originally written by @vicb


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

from language.

kevmoo avatar kevmoo commented on May 28, 2024

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


cc @gbracha.

from language.

gbracha avatar gbracha commented on May 28, 2024

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.

from language.

lrhn avatar lrhn commented on May 28, 2024

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


cc @floitschG.

from language.

DartBot avatar DartBot commented on May 28, 2024

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?

from language.

kevmoo avatar kevmoo commented on May 28, 2024

RE #­11 from pchalin:

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

from language.

DartBot avatar DartBot commented on May 28, 2024

This comment was originally written by @chalin


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

from language.

yjbanov avatar yjbanov commented on May 28, 2024

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

from language.

darting avatar darting commented on May 28, 2024

Hey, any news?

from language.

munificent avatar munificent commented on May 28, 2024

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.

from language.

kevmoo avatar kevmoo commented on May 28, 2024

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

I'm tempted to close this as ~fixed.

@munificent @leafpetersen ?

from language.

mateusfccp avatar mateusfccp commented on May 28, 2024

@kevmoo
How is this redundant to extension methods?

from language.

lrhn avatar lrhn commented on May 28, 2024

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

from language.

venkatd avatar venkatd commented on May 28, 2024

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.

from language.

yarn-rp avatar yarn-rp commented on May 28, 2024

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

from language.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.