Comments (33)
Here's a vote for the F#-like "|>" syntax. We avoid overloading the behavior of bit-wise.
Set owner to @gbracha.
from language.
I love |> in Elixir! I miss the pipe operator alot in Dart
from language.
. Doing
someExpression |> print
looks nice, but doingsomeExpression |> 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.
FYI: A proposal for adding the simple-but-useful pipeline operator to JavaScript.. /cc @munificent @kevmoo @kwalrath
from language.
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.
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.
@chalin – you know I'm a fan!
from language.
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.
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.
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.
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.
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.
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.
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.
Removed Type-Defect label.
Added Type-Enhancement, Area-Language, Triaged labels.
from language.
This comment was originally written by @zoechi
This is a nice idea but | is already in use for bitwise OR.
from language.
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.
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.
This comment was originally written by @vicb
see https://code.google.com/p/dart/issues/detail?id=18485
from language.
Issue dart-lang/sdk#18485 has been merged into this issue.
cc @gbracha.
from language.
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.
Issue dart-lang/sdk#18516 has been merged into this issue.
cc @floitschG.
from language.
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.
RE #11 from pchalin:
Please open a separate issue. We could offer method piping without providing a general model for method composition.
from language.
This comment was originally written by @chalin
Doen: https://code.google.com/p/dart/issues/detail?id=18522
from language.
Has anyone started or is anyone interested in starting a DEP for this?
from language.
Hey, any news?
from language.
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.
This seems redundant to extension methods – https://dart.dev/guides/language/extension-methods
I'm tempted to close this as ~fixed.
from language.
@kevmoo
How is this redundant to extension methods?
from language.
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.
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.
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 likeextension 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 prefervalue = 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)
- Destructuring records in functions HOT 5
- Lower the directory depth cost of packages HOT 2
- Can an augmentation library be an entry point? HOT 9
- How do we merge augmentation imports? HOT 6
- Grammar rule adjustments for augmentation libraries HOT 1
- False analyzer warnings when using nullable extensions HOT 2
- Augmentation libraries can't be main libraries as well? HOT 2
- Augmenting declarations cannot occur outside augmentation libraries, right? HOT 4
- Proposal: remove special analyzer behavior for await expressions with "null context". HOT 5
- Proposal: remove special front end behavior for await expressions with context `dynamic`. HOT 2
- Proposal: align front end behavior with analyzer for if-null expressions in context `dynamic`. HOT 3
- Support debugging Dart projects with compilation errors HOT 3
- Pattern matching allows you to refer to its own scope HOT 3
- Proposal: add a context for RHS of equality operations. HOT 1
- matching default values in switch statements (with record patterns) HOT 4
- Failing analysis of macro test prevents other actions HOT 1
- [augmentation-libraries] Missing grammar rule for extension and enum augmentations? HOT 6
- Assignment expressions in `if`-statements don't correctly promote nullable variables HOT 4
- Eliminate symbol literals with several identifiers? HOT 10
- Update the spec parser to take stop and continuation tokens into account for `<typeArguments>` as a selector
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from language.