Remix.run Logo
avaq 8 days ago

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.