| ▲ | bapak 8 days ago |
| Meanwhile the JS world has been waiting for 10 years for this proposal, which is still in stage 2 https://github.com/tc39/proposal-pipeline-operator/issues/23... |
|
| ▲ | avaq 8 days ago | parent | next [-] |
| Not only have we been waiting for 10 years, the most likely candidate to go forward is not at all what we wanted when the proposal was created: We wanted a pipe operator that would pair well with unary functions (like those created by partial function application, which could get its own syntax), but that got rejected on the premise that it would lead to a programming style that utilizes too many closures[0], and which could divide the ecosystem[1]. Yet somehow PHP was not limited by these hypotheticals, and simply gave people the feature they wanted, in exactly the form it makes most sense in. [0]: https://github.com/tc39/proposal-pipeline-operator/issues/22...
[1]: https://github.com/tc39/proposal-pipeline-operator/issues/23... |
| |
| ▲ | lexicality 8 days ago | parent | next [-] | | Am I correct in my understanding that you're saying that the developers of the most widely used JS engine saying "hey we can't see a way to implement this without tanking performance" is a silly hypothetical that should be ignored? | | |
| ▲ | jeroenhd 7 days ago | parent | next [-] | | With JS' async/await system basically running on creating temporary closures, I don't think things will change all that much to be honest. Furthermore, I don't see why engines should police what is or isn't acceptable performance. Using functional interfaces (map/forEach/etc.) is slower than using for loops in most cases, but that didn't stop them from implementing those interfaces either. I don't think there's that much of a performance impact when comparing const x = fun1(abc);
const y = fun2(x);
const z = fun3(y);
fun4(z);
and abc |> fun1 |> fun2 |> fun3 |> fun4
especially when you end up writing code like fun1(abc).then( (x) => fun2(x)).then( (y) => fun3(y)).then((z) => fun4(z))
when using existing language features. | | |
| ▲ | ufo 7 days ago | parent [-] | | The problem they were discussion in the linked Github issue are pipelines where the functions receive more than one argument. const x = fun1(a, 10)
const y = fun2(x, 20)
const z = fun3(y, 30)
In this case the pipeline version would create a bunch of throwaway closures. a |> ((a) => fun1(a, 10))
|> ((x) => fun2(x, 20))
|> ((y) => fun3(y, 30))
|
| |
| ▲ | avaq 8 days ago | parent | prev | next [-] | | They can't implement function application without tanking performance? I find that hard to believe. Especially considering that function application is already a commonly used (and, dare I say: essential) feature in the language, eg: `Math.sqrt(2)`. All we're asking for is the ability to rewrite that as `2 |> Math.sqrt`. What they're afraid of, my understanding goes, is that people hypothetically, may start leaning more on closures, which themselves perform worse than classes. However I'm of the opinion that the engine implementors shouldn't really concern themselves to that extent with how people write their code. People can always write slow code, and that's their own responsibility. So I don't know about "silly", but I don't agree with it. Unless I misunderstood and somehow doing function application a little different is actually a really hard problem. Who knows. | | |
| ▲ | nilslindemann 7 days ago | parent [-] | | Your simple example (`2 |> Math.sqrt`) looks great, but when the code gets more complex, then the advantage of the pipe syntax is less obvious. For example, foo(1, bar(2, baz(3)), 3)
becomes something like 1 (2, (3 |> baz) > bar), 3 |> foo
or (3 |> baz) |> (2, % |> bar) |> (1, %, 3 |> foo)
That looks like just another way to write a thing in JavaScript, and it is not easier to read. What is the advantage? | | |
| ▲ | avaq 7 days ago | parent [-] | | Uhm, don't do it, then. That's like arguing that the addition of the ternary operator is a bad one because not all if/else blocks look better when translated into it. The goal is to linearlize unary function application, not to make all code look better. | | |
| ▲ | sir_eliah 7 days ago | parent [-] | | I think the commenter meant that once the new syntax is approved and adopted by the community, you have no choice to not use the syntax. You'll eventually change your project and will be forced to deal with reviewing this code. | | |
| ▲ | dotancohen 5 days ago | parent [-] | | _You_ might not use it, but somebody's going to send a PR, or some LLM is going to spit it out, or some previous maintainer put it in there, or will be in some tutorial, or it will be in some API documentation. You are going to have to deal with it as a mess at some point. One of the downfalls of perl was the myriad of ways of doing any particular thing. We would laugh that perl was a write only language - nobody knew all the little syntax tricks. |
|
|
|
| |
| ▲ | hajile 7 days ago | parent | prev | next [-] | | Closures won over OOP in Javascript a long time ago (eg, React switching from classes to functions + closures), but they still keep trying to force garbage like private variables on the community. Loads of features have been added to JS that have worse performance or theoretically enable worse performance, but that never stopped them before. Some concrete (not-exhaustive) examples: * Private variables are generally 30-50% slower than non-private variables (and also break proxies). * let/const are a few percent slower than var. * Generators are slower than loops. * Iterators are often slower due to generating garbage for return values. * Rest/spread operators hide that you're allocating new arrays and objects. * Proxies cause insane slowdowns of your code. * Allowing sub-classing of builtins makes everything slow. * BigInt as designs is almost always slower than the engine's inferred 31-bit integers. Meanwhile, Google and Mozilla refuse to implement proper tail calls even though they would INCREASE performance for a lot of code. They killed their SIMD projects (despite having them already implemented) which also reduced performance for the most performance-sensitive applications. It seems obvious that performance is a non-issue when it's something they want to add and an easy excuse when it's something they don't want to add. | | |
| ▲ | tracker1 7 days ago | parent | next [-] | | I wish I could upvote this more than once. I really liked the F# inspired pipe operator proposal and even used it a bit when I used to lean on 6to4/babel more, but it just sat and languished forever it seems. I can't really think of any other language feature I've seen since that I would have wanted more. The new Temporal being one exception. | | |
| ▲ | hajile 7 days ago | parent | next [-] | | It seems like none of the really good language proposals have much traction. Proper Tail Calls have been in the language for TEN YEARS now, but v8 and Spidermonkey still violate the spec and refuse to implement for no good reason. Record/tuple was killed off despite being the best proposed answer for eliminating hidden class mutation, providing deep O(1) comparisons, and making webworkers/threads/actors worth using because data transfer wouldn't be a bottleneck. Pattern matching, do expressions, for/while/if/else expressions, binary AST, and others have languished for years without the spec committee seemingly caring that these would have real, tangible upsides for devs and/or users without adding much complexity to the JIT. I'm convinced that most of the committee is completely divorced from the people who actually use JS day-to-day. | |
| ▲ | jedwards1211 7 days ago | parent | prev [-] | | I think it’s mainly because they struggled to get consensus on which syntax to go with for pipelines, since people were divided into three different camps. I wish they would just standardize all three options with a slightly different operator for each one |
| |
| ▲ | int_19h 7 days ago | parent | prev [-] | | > * Rest/spread operators hide that you're allocating new arrays and objects. Only in function calls, surely? If you're using spread inside [] or {} then you already know that it allocates. | | |
| ▲ | hajile 7 days ago | parent [-] | | It used to be said that "Lisp programmers know the value of everything and the cost of nothing." This applies to MOST devs today in my experience and doubly to JS and Python devs as a whole largely due to a lack of education. I'm fine with devs who never went to college, but it becomes an issue when they never bothered to study on their own either. I've worked with a lot of JS devs who have absolutely no understand of how the system works. Allocation and garbage collection are pure magic. They also have no understanding of pointers or the difference between the stack and heap. All they know is that it's the magic that makes their code run. For these kinds of devs, spread just makes the object they want and they don't understand that it has a performance impact. Even among knowledgeable devs, you often get the argument that "it's fast enough" and maybe something about optimizing down the road if we need it. The result is a kind of "slow by a thousand small allocations" where your whole application drags more than it should and there's no obvious hot spot because the whole thing is one giant, unoptimized ball of code. At the end of the day, ease of use, developer ignorance, and deadline pressure means performance is almost always the dead-last priority. |
|
| |
| ▲ | nobleach 7 days ago | parent | prev [-] | | I know that was the reasoning for the Records/Tuples proposal being shot down. I haven't dug too deeply into the pipeline operators other than to get a feel for both proposals. Most of the more interesting proposals tend to languish these days. When you look at everything that's advanced to Stage 3-4, it's like. "ok, I'm certain this has some amazing perf bump for some feature I don't even use... but do I really care?" |
| |
| ▲ | xixixao 8 days ago | parent | prev | next [-] | | I guess partially my fault, but even in the article, you can see how the Hack syntax is much nicer to work with than the functional one. Another angle is “how much rewriting does a change require”, in this case, what if I want to add another argument to the rhs function call. (I obv. don’t consider currying and point-free style a good solution) | |
| ▲ | WorldMaker 7 days ago | parent | prev [-] | | I am wondering if PHP explicitly rejecting Hack-style pipes (especially given the close ties between PHP and Hack, and that PHP doesn't have partial application, but JS does, sort of, though its UX could be improved) will add leverage to the F#-style proposal over the Hack-style. It may be useful data that the TC-29 proposal champions can use to fight for the F# style. |
|
|
| ▲ | wouldbecouldbe 8 days ago | parent | prev | next [-] |
| It’s really not needed, syntax sugar. With dots you do almost the same. Php doesn’t have chaining. Adding more and more complexity doesn’t make a language better. |
| |
| ▲ | chilmers 7 days ago | parent | next [-] | | I'm tired of hearing the exact same arguments, "not needed", "just syntax sugar", "too much complexity", about every new syntax feature that gets added to JS. Somehow, once they are in the language, nobody's head explodes, and people are soon using them and they become uncontroversial. If people really this new syntax will make it harder to code in JS, show some evidence. Produce a study on solving representative tasks in a version of the language with and without this feature, showing that it has negative effects on code quality and comprehension. | | | |
| ▲ | bapak 8 days ago | parent | prev | next [-] | | Nothing is really needed, C89 was good enough. Dots are not the same, nobody wants to use chaining like underscore/lodash allowed because it makes dead code elimination impossible. | | |
| ▲ | pjmlp 8 days ago | parent [-] | | K&R C was good enough for UNIX System V, why bother with C89. | | |
| |
| ▲ | troupo 8 days ago | parent | prev | next [-] | | > With dots you do almost the same. Keyword: almost. Pipes don't require you to have many different methods on every possible type: https://news.ycombinator.com/item?id=44794656 | |
| ▲ | hajile 7 days ago | parent | prev | next [-] | | Chaining requires creating a class and ensuring everything sticks to the class and returns it properly so the chain doesn't blow up. As you add more options and do more stuff, this becomes increasingly hard to write and maintain. If I'm using a chained library and need another method, I have to understand the underlying data model (a leaky abstraction) and also must have some hack-ish way of extending the model. As I'm not the maintainer, I'm probably going to cause subtle breakages along the way. Pipe operators have none of these issues. They are obvious. They don't need to track state past the previous operator (which also makes debugging easier). If they need to be extended, look at your response value and add the appropriate function. Composition (whether with the pipe operator or not) is vastly superior to chaining. | |
| ▲ | Martinussen 7 days ago | parent | prev | next [-] | | When you say chaining, do you mean autoboxing primitives? PHP can definitely do things like `foo()->bar()?->baz()`, but you'd have to wrap an array/string yourself instead of the methods being pulled from a `prototype` to use it there. | |
| ▲ | purerandomness 7 days ago | parent | prev | next [-] | | If your team prefers not to use this new optional feature, just enable a PHPStan rule in your CI/CD pipeline that prevents code like this getting merged. | |
| ▲ | EGreg 8 days ago | parent | prev | next [-] | | It’s not really chaining More like thenables / promises | | |
| ▲ | wouldbecouldbe 8 days ago | parent [-] | | It looks like chaining, but with possibility of adding custom functions? | | |
| ▲ | bapak 8 days ago | parent [-] | | It's chaining without having to vary the return of each function. In JS you cannot call 3.myMethod(), but you could with 3 |> myMethod | | |
| ▲ | cyco130 8 days ago | parent | next [-] | | It requires parentheses `(3).myMethod()` but you can by monkey patching the Number prototype. Very bad idea, but you absolutely can. | | | |
| ▲ | EGreg 7 days ago | parent | prev [-] | | Not only that In chaining, methods all have to be part of the same class. In C++ we had this stuff ages ago, it’s called abusing streaming operators LMAO |
|
|
| |
| ▲ | te_chris 8 days ago | parent | prev [-] | | Dots call functions on objects, pipe passes arguments to functions. Totally missing the point. |
|
|
| ▲ | fergie 8 days ago | parent | prev | next [-] |
| Good- the [real world examples of pipes in js](https://github.com/tc39/proposal-pipeline-operator?tab=readm...) are deeply underwhelming IMO. |
|
| ▲ | epolanski 7 days ago | parent | prev | next [-] |
| Sadly they went obsessing over pipes with promises which don't fit the natural flow. Go explain them that promises already have a natural way to chain operations through the "then" method, and don't need to fit the pipe operator to do more than needed. |
|
| ▲ | lacasito25 8 days ago | parent | prev | next [-] |
| in typescript we can do this let res
res = op1()
res = op2(res.op1)
res = op3(res.op2) type inference works great, and it is very easy to debug and refactor. In my opinion even more than piping results. Javascript has enough features. |
|
| ▲ | 77pt77 7 days ago | parent | prev | next [-] |
| Best you'll git will be 3 new build systems and 10 new frameworks |
|
| ▲ | defraudbah 8 days ago | parent | prev [-] |
| do not let me start on monads in golang... both are going somewhere and super popular though |