tc39 / proposal-pipeline-operator

A proposal for adding a useful pipe operator to JavaScript.
http://tc39.github.io/proposal-pipeline-operator/
BSD 3-Clause "New" or "Revised" License
7.52k stars 108 forks source link

unconvincing example in readme part Hack pipes might be simpler to use #235

Open lightmare opened 3 years ago

lightmare commented 3 years ago

Quoting from Hack pipes might be simpler to use

For example, with Hack pipes, value |> someFunction + 1 is invalid syntax and will fail early. There is no need to recognize that someFunction + 1 will not evaluate into a unary function. But with F# pipes, value |> someFunction + 1 is still valid syntax – it’ll just fail late at runtime, because someFunction + 1 isn’t callable.

This assumes |> having lower precedence than arithmetic operators. To me that wouldn't make much sense in F# style. I'd rather read the above as (value |> someFunction) + 1.

I've seen arguments for lower precedence, mostly revolving around logical operators — e.g. for x |> (f || g) to not require the parentheses. I would argue that (x |> f) || y is an equally valid use-case, so the choice of below/above logical operators would be cosmetic, whereas the choice of below/above arithmetic operators would be practical.

tabatkins commented 3 years ago

Yeah, there's some significant precedence issues here. The logical operators are an important one, where going either way has reasonable arguments. But more importantly is the relation of |> to =>; F#-style has to have unparenthesized arrow-funcs in pipe bodies to be competitive with any other style (otherwise you're paying a five-character tax, split across both sides of the expression), and => is already very low precedence.

(Note that pipe() gets unparenthesized arrow funcs for free, since , is lower precedence than =>.)

Allowing unparenthesized arrows would already require some significant parsing hackery even with equivalent precedence; having |> be lower precedence would, iiuc, be even worse.

js-choi commented 3 years ago

Yes, the assumption of this example was that F# pipes would have looser precedence than =>, in order to allow unparenthesized input |> x => x + 1.

I would love to know if there is a more compelling way of illustrating that F# pipes would require the developer to distinguish between “expression that resolves to an unary function” versus “any other expression” – and to remember to add an arrow-function wrapper around the latter case. Suggestions or pull requests are welcome. : )

lightmare commented 3 years ago

But more importantly is the relation of |> to =>; F#-style has to have unparenthesized arrow-funcs in pipe bodies to be competitive with any other style (otherwise you're paying a five-character tax, split across both sides of the expression), and => is already very low precedence.

I disagree. In my opinion, unparenthesized arrows in F# pipe are utterly broken — either they nest, or you need to twist the grammar to end them early. Either way, I don't think they're worth the cost; the avoided tax would be paid by the reader.

That being said, whether unparenthesized arrows are allowed or not does not inform the choice of precedence. Since allowing them would require parsing hackery as you wrote, you'd do this hackery regardless of the pipe's precedence relative to other operators — the interpretation currently implemented in Babel violates the precedence of assignment inside unparethesized arrow functions.

lightmare commented 3 years ago

I would love to know if there is a more compelling way of illustrating that F# pipes would require the developer to distinguish between “expression that resolves to an unary function” versus “any other expression” – and to remember to add an arrow-function wrapper around the latter case.

That is kinda inherent in pipe-into-function semantics, isn't it? Also sounds like what we do with [].map(...) et al.

My issue was about the "will fail at runtime" part. That one doesn't seem inherent to F# pipe. With higher precedence and required parentheses around arrow functions, it would not fail.

edit: going back to illustrating the distinction: I've seen refactoring examples around here, passing additional arguments to what was originally a "resolves to function" expression in the pipeline, you need to wrap the whole thing in an arrow — which is admittedly more tedious with F# than with Hack (where you pay upfront, so later edits are cheaper).

mAAdhaTTah commented 3 years ago

I disagree. In my opinion, unparenthesized arrows in F# pipe are utterly broken — either they nest, or you need to twist the grammar to end them early. Either way, I don't think they're worth the cost; the avoided tax would be paid by the reader.

This is true and something that hasn't come up much recently in the arguments around F#. I will say that requiring parens around arrows in the F# version significantly increases the tax of that version of the pipe.

samhh commented 3 years ago

@mAAdhaTTah In my experience idiomatic usage of userland pipelines is to abstract out and name your functions. The same idiom exists in Haskell, albeit it's more ergonomic there with where clauses. Wrapping the few lambdas that remain is a very minor tax in my view.

lozandier commented 2 years ago

@mAAdhaTTah The examples should also point out the differences of using both syntaxes to do class mixins and so on where I personally think it'll be even harder for Hack pipes to be suggested to be easier to read.

It seems too simple use cases are being demonstrated to readers instead of the more intermediate and advanced use cases such as the one I mentioned that would forces more conviction on readers to decide what's simpler between the two.

And that's not going into the fact various JS functional programming communities such as RxJS community have organically preferred and garnered interest in a pipe operator more aligned to F#-style than Hack-style historically.

It is also no coincidence the most meaningful pushback to the Hack Pipe style have been members of such communities and maintainers of the libraries voicing their concerns on behalf of such communities.

There's many benefits Hack-style proponents have communicated towards successful stage 2 adoption; IMO, readability and familiarity of the Hack-style semantics vs F# isn't necessarily one of them nor the strongest.