Skip to content

A simpler, proposed alternative to existing proposals #302

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

Closed
matthew-dean opened this issue Jan 22, 2024 · 5 comments
Closed

A simpler, proposed alternative to existing proposals #302

matthew-dean opened this issue Jan 22, 2024 · 5 comments

Comments

@matthew-dean
Copy link

matthew-dean commented Jan 22, 2024

Introduction

One thing that immediately caught my eye about this proposal is that it feels like it over-thinks this operator to want to apply it to every conceivable scenario, instead of the lowest (and largest) hanging fruit, which is function currying.

That is, it feels like both Hack pipes and F# pipes surfaced because the proposed syntaxes handle any arbitrary action that one might take with a result.

I feel like this not only multiplies the complexity of a possible pipe operator, but also conflates actions that are more easily handled by other syntax. Then, the proposal goes on to propose, as an alternative, partial-function application, which then again seems (IMO) orders of magnitude more complex than this feature / operator needs to be (which may be why variations of this proposal have been rejected so many times)?

Here's what I would call the over-complexity of each proposal:

  1. Hack pipes - the necessity of introducing a new placeholder operator (%)
  2. F# pipes - the necessity of creating a new function callback
  3. Partial function application - the necessity of creating 2 (?) new syntaxes

The KISS Pipe Operator Proposal

Advantages

  1. Instead of creating a multitude of new syntaxes, there would be a single new syntax, |> and that's it.
  2. Much like .bind() or .call() function methods, it would partially bind parameters from the left and allow a callable function. However, unlike the PFA proposal, it doesn't require additional syntax. The return value would simply be applied to the first parameter of the next function, then called. (So this would look more familiar to JS devs.)
  3. It takes the simplicity of the Hack proposal, the versatility of the F# proposal, and the intuitiveness of partial-function application.

Description

The pipe takes an evaluated result and pipes it to (usually) a function-call-like syntax, or a function. If the pipe target resembles a function call, then the result of the pipe will be prepended to the argument list. If the pipe target is a function, then the function will be called and will pass in the result as the first and only argument.

Examples

// contrived but for the sake of illustration
const add = (a, b) => {
  console.log(`a: ${a}, b: ${b}`)
  return a + b 
}
// this is the equivalent of add.call(this, 1, 2)
const result = 1 |> add(2) // 'a: 1, b: 2'

For most scenarios, this can result in simplifications like:

const result = 1 |> add(2) |> multiply(3) |> await log()

Examples from Hack syntax

let result = value |> foo() // for unary function calls,
let result = value |> foo(1) // for n-ary function calls,
let result = value |> foo(2, 3) // `foo` is receiving 3 arguments

Advanced usages

I would place all other usages in the advanced usages category. These end up looking like the F# examples but note that they are not necessary for most use cases.

value |> x => foo(1, x) // for some scenario where you want to flip argument order
value |> x() |> y => y.foo() // for method calls,
value |> x => ((x + 1) |> someLog()) // arithmetic and an inner pipe
value |> x => [x, 0] // for array literals,
value |> x => ({foo: x}) // for object literals,
value |> x => `${x}` // for template literals,
value |> x => new Foo(x) // for constructing objects,

Questions

Q: Doesn't this imply that the parser would need to lookahead to determine if it's a pipe-able function call or function reference?
A: Yes but arrow functions already have lookahead, so its not new to the grammar. Either the right-hand expression following |> is a function call or its assumed to be a function reference.

Q: Is this the best of both worlds? And far simpler than both the Hack and F# proposals, whilst being more intuitive and powerful than both?
A: Yes.

Q: Why not support %?
A: Because syntax is hard, and you absolutely do not need it. The existing Hack-based proposal is just needlessly complicated, as is the existing F#-based proposal. Just pipe to function calls, except when you absolutely need a more advanced scenario, in which case the arrow syntax works perfectly. The existing proposals, and issues in this repo focus on "but then you can't do X" which may turn out to be true, but I don't see how that is disqualifying, if X itself adds a great deal of complexity into the syntax of what should be a simple concept, which is piping.

In fact, I would even propose that if the Advanced Usage is distasteful initially for reasons given for the rejection of the F# proposal that still apply, then I would discard it (or discard it for Stage 1) for just partially-applied function calls, which would still be powerful with the |>. But IMO, it should be trivial to support both, without incurring the automatic performance penalty of the F# proposal, which ALWAYS requires function definitions after the pipe.

In Conclusion

I think this proposal could get legs if:

  1. It just focuses on the power of functions, and the existing partial-function application syntaxes of .bind() and .call() (left-to-right application)
  2. It omits any new syntax other than the pipe |>
@noppa
Copy link
Contributor

noppa commented Jan 22, 2024

This seems to be the same as "Elixir-style" pipeline, which was discussed in #143. Plus the "implicit alrrow-function call" extension to it.

@ljharb
Copy link
Member

ljharb commented Jan 22, 2024

which is function currying

this isn't something that is universally desired or desirable, and in particular, engines actively do not want to encourage a practice that creates lots of functions.

@hesxenon
Copy link

it also isn't something that is universally undesired or undesirable. I get these points and I've been getting them for the past years of following this proposal, but is "the engine" there to have an easy life or to support developers?

If performance were even remotely as big of an issue as it's sometimes made out to be in this proposal we'd probably all be writing assembler and ideas like "super slow and inefficient javascript on the backend" wouldn't ever have come to fruition.

The problem with this kind of argument is that on one hand it dismisses the possibility of a technical solution and on the other it ignores that performance is - and will always be - the responsibility of the developer. If the number of functions is the performance bottleneck in an application I'll go out on a limb and say that JS probably wasn't a good fit for that application to begin with.

@ljharb
Copy link
Member

ljharb commented Jan 22, 2024

Changes to the JS language requires consensus - so if there are objectors, it doesn't matter that there are also supporters.

@tabatkins
Copy link
Collaborator

The "just solve function currying" approach was already discussed, and rejected by the committee. See #206 for extensive discussion on this topic.

@tc39 tc39 locked as resolved and limited conversation to collaborators Jan 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants