-
Notifications
You must be signed in to change notification settings - Fork 205
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
Comments
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. |
Removed Type-Defect label. |
This comment was originally written by @zoechi This is a nice idea but | is already in use for bitwise OR. |
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. |
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 |
This comment was originally written by @vicb |
Issue dart-lang/sdk#18485 has been merged into this issue. cc @gbracha. |
Here's a vote for the F#-like "|>" syntax. We avoid overloading the behavior of bit-wise. Set owner to @gbracha. |
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. |
Issue dart-lang/sdk#18516 has been merged into this issue. cc @floitschG. |
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.
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? |
RE #11 from pchalin: Please open a separate issue. We could offer method piping without providing a general model for method composition. |
This comment was originally written by @chalin |
Has anyone started or is anyone interested in starting a DEP for this? |
@chalin – you know I'm a fan! |
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? |
I love |> in Elixir! I miss the pipe operator alot in Dart |
Hey, any news? |
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. |
This is perfect and used in another languages as well, like Elm, etc. |
This seems redundant to extension methods – https://dart.dev/guides/language/extension-methods I'm tempted to close this as ~fixed. |
@kevmoo |
You can't get proper typing with extension operators. 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 You definitely can't do something variadic like: int Function(int, String, bool) f = ...;
42 |> f("a", false); which I'd also like. |
I thought kevmoo meant that extension method serve the same purpose of the pipe operator. I agree with that. Extension methods are best seen as just another syntax for a top level function (or static function). 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. |
You can definitely do something similar to a function pipe by writing sufficiently many extension methods. extension X on double {
double sin() => math.sin(this);
double sqrt() => math.sqrt(this);
} before you can just write |
It's a good point that having an easy currying syntax makes it less important to include it in the pipe itself. 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 |
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
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 |
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 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. |
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 So, maybe we should make it an implicitly bound variable named, say, 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 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). Also, pipe is not essential to having statements inside expressions. You can already do You can do 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 Using the pipe syntax doesn't seem like it adds that much value. Even without that extension, you can write (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. |
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 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 Nevertheless, considering that these developers have access to both options, you may assume they never use the pipe operator because the |
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 |
This totally depends on the definition of the function If you define
Similarly,
will be
If you define Actually, during this thread, I have seen multiple comments by whom perhaps never used "pipe-operator" in real life. Function/pipe operator |
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.
The text was updated successfully, but these errors were encountered: