Skip to content
This repository has been archived by the owner on Jan 26, 2022. It is now read-only.

Support for arg destructuring #4

Closed
peey opened this issue Apr 9, 2021 · 7 comments
Closed

Support for arg destructuring #4

peey opened this issue Apr 9, 2021 · 7 comments
Assignees

Comments

@peey
Copy link

peey commented Apr 9, 2021

I think one difficult case for this proposal would be when args need to be de-structured.

In the alternate https://github.com/valtech-nyc/proposal-fsharp-pipelines/blob/master/README.md proposal, we might write

... |> ({x, y}) => new Foo(x, y) |> ...

but here we'd perhaps have to write

... |> () => { const {x, y} = #; return new Foo(x, y) } |> ...

I've taken a simplistic example but you might have complex de-structuring (e.g ({x, y : [a, ...b], ...z}) => ...) so we can't just give up on the de-structuring and use ?.x and ?.y instead.

Are there any solutions that this proposal can put forward to this problem while preserving the other benefits already outlined by it?

@js-choi
Copy link
Collaborator

js-choi commented Apr 10, 2021

Thanks for the comment. The case of complex destructuring is indeed a tradeoff.

Let’s elaborate that last example you gave into the following code. Perhaps we want to inline the function’s first statement into the second statement, while flattening both statements’ nested expressions:

async input => {
  const { x, y, ...z } = (await preprocess(input)).next();
  return (new Foo(x, y, ...z)).log();
}

With F# pipes, this would look like:

async input => (
  input |> preprocess |> await |> v => v.next()
    |> ({ x, y, ...z }) => new Foo(x, y, ...z)
    |> v => v.log()
)

With Hack pipes, you could still use create a unary arrow function that destructures its argument; you only have to explicitly call it with (#):

async input =>
  input |> await preprocess(#) |> #.next()
    |> (({ x, y, ...z }) => new Foo(x, y, z))(#)
    |> #.log();

You could also use a do expression when TC39 finishes standardizing do expressions:

async input =>
  input |> await preprocess(#) |> #.next()
    |> do { const { x, y, ...z } = #; new Foo(x, y, z) }
    |> #.log();

But, because of the complex destructuring, this is a case where a single pipeline (whether F# or Hack style) might not be the best choice. The best, flattest most-readable choice might simply be to split the pipeline into two, with a usual const declaration at the function body’s top level.

async input => {
  const { x, y, ...z } = input |> await preprocess(#) |> #.next();
  return new Foo(x, y, ...z) |> #.log();
}

After all, the point of pipes is that they can untangle without having to name a lot of temporary variables…but, with destructuring, you’re naming a bunch of variables anyway. So perhaps you might as well split the pipeline and use a const declaration.

I’ll keep this issue open for now, in case there’s further discussion to be had about this.

@js-choi js-choi self-assigned this Apr 10, 2021
@js-choi
Copy link
Collaborator

js-choi commented Jul 29, 2021

I don’t think there’s more actionable to do here, so I’ll close it. At most I’d write a section in the explainer saying, “Simple destructuring can be done with properties; complex destructuring probably should be put in a separate pipeline or function anyway.” If there’s more discussion to be had, we can open a new discussion. Thanks!

@js-choi js-choi closed this as completed Jul 29, 2021
@peey
Copy link
Author

peey commented Jul 30, 2021

Thanks for documenting this.

You can also propose alternate syntax e.g. |> #({x, y}) => new Foo(x, y) |> to perhaps switch from implicit functions mode to an "explicit" functions mode, but I don't know how useful this suggestion is.

I'd also like to add that the arg-destructuring cumbersomeness is an example of a general downside of introducing new syntax -- that the introducer now has to define how it'll interact with the other language concepts. If existing concepts are used (e.g. single-arg functions already support de-structuring) then this downside doesn't arise.

It'd be useful to add a section titled "limitations" or "trade-offs" which mentions this and any other known trade-offs, which may help in evaluating this proposal against other proposals.

@js-choi js-choi reopened this Jul 30, 2021
@js-choi
Copy link
Collaborator

js-choi commented Jul 30, 2021

I’m not sure why GitHub deleted my comment, but basically I agreed that I should elaborate more on the trade-offs, which I then did in f53a6e3. I also pointed out both proposals reuse existing language concepts (one uses the expression; one uses the unary function), both with different interactions with other language concepts (like destructuring, function-scoped operations, object/array literals, keywords, etc.). Thanks again!

@chocolateboy
Copy link

chocolateboy commented Jul 31, 2021

[the] flattest most-readable choice

While I think it's good to bring up examples where this proposal may be less ergonomic than the alternative, I'm not sure either proposal is necessarily optimal in terms of flatness/readability for this particular example.

I doubt we'll end up replacing method chaining in Lodash, jQuery etc. with this syntax, and it's worth remembering that promises already provide a way to keep this kind of pipeline flat and readable, e.g.:

input => preprocess(input)
    .then(it => it.next())
    .then(({ x, y, ...z }) => new Foo(x, y, ...z).log())

@tabatkins
Copy link
Collaborator

I doubt we'll end up replacing method chaining in Lodash, jQuery etc. with this syntax, and it's worth remembering that promises already provide a way to keep this kind of pipeline flat and readable, e.g.:

Note that Hack-style is explicitly not trying to replace method-chaining; it's about bringing the benefits of method-chaining to all other calling styles! ^_^

That way we don't have to go all-in with methods on uber-objects like jQuery does; we can instead use a nice mix of functions and methods as appropriate, like most codebases do today. But existing uber-objects like jQuery are perfectly compatible with Hack-style pipes, and the pipes help in those circumstances when you do have to break out of the uber-object and pass it to a normal function temporarily.

@chocolateboy
Copy link

pipes help in those circumstances when you do have to break out of [method chaining] and pass it to a normal function temporarily

Sure, but the point is this isn't a compelling example of one of those cases.

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

4 participants