Skip to content
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

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

Open
lightmare opened this issue Sep 28, 2021 · 7 comments
Open
Labels
documentation Improvements or additions to documentation question Further information is requested

Comments

@lightmare
Copy link

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.

@js-choi js-choi added documentation Improvements or additions to documentation question Further information is requested labels Sep 28, 2021
@tabatkins
Copy link
Collaborator

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
Copy link
Collaborator

js-choi commented Sep 28, 2021

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
Copy link
Author

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
Copy link
Author

lightmare commented Sep 28, 2021

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
Copy link
Collaborator

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
Copy link

samhh commented Sep 30, 2021

@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
Copy link

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation question Further information is requested
Projects
None yet
Development

No branches or pull requests

6 participants