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

Proposal 1: Bring back unparen'd arrow functions #104

Closed
mAAdhaTTah opened this issue Mar 12, 2018 · 42 comments
Closed

Proposal 1: Bring back unparen'd arrow functions #104

mAAdhaTTah opened this issue Mar 12, 2018 · 42 comments

Comments

@mAAdhaTTah
Copy link
Collaborator

I've mentioned this a few times, but I would like to officially include this comment from @pygy as part of Proposal 1. We'll see if this is feasible as part of the babel plugin implementation, but if it is, would anyone be opposed? That would mean pipelines would require parens to write within an arrow function:

x => (x |> g |> f |> h)

This would introduce a minor footgun, as this:

x => x |> g |> f |> h

would parse like this:

(x => x) |> g |> f |> h

Thoughts?

@js-choi
Copy link
Collaborator

js-choi commented Mar 12, 2018

Just to reemphasize @mAAdhaTTah’s point: This applies to Proposal 1. This does not apply to the other proposals. The point of this would be to make using arrow functions within pipelines easier, which is important to Proposal 1. However, it would make using pipelines in arrow functions harder.


Arrow functions without braces can currently be terminated only by commas and semicolons…I think. This change would amend them to also be terminated by |>.

I think the next thing to do here is to sketch the formal grammar—particularly for AssignmentExpression and for ConciseBody. Then we could figure things out from there. If you need any help with interpreting or modifying the spec, @mAAdhaTTah, let me know.

@littledan
Copy link
Member

Personally I'm fine with just making a decision about the precedence like this, but other TC39 members such as @michaelficarra were a bit more hesitant when we discussed this in the past. Even as you're leading this variant, it could be helpful to work to build consensus with as many people as possible; maybe he'd be available to discuss this issue.

@mAAdhaTTah
Copy link
Collaborator Author

@littledan Yeah, I wasn't sure how controversial this change might be, but given that I think arrow functions are likely to be a common use case for the F#-style pipeline, I think this would be an improvement. Definitely interested in hearing concerns, hence the issue, but I'm not sure who needs to weigh in here.

@littledan
Copy link
Member

I think it'd be good to reach out to the people who expressed concerns previously.

What about the other idea to have pipeline placeholders? Didn't we toss around the idea of "backporting" placeholders to the F# proposal, but restricted to be only used in pipelines?

@mAAdhaTTah
Copy link
Collaborator Author

I don't think anyone has yet expressed concerns about this particular change yet; I haven't seen much of a response to this proposal in general, despite bringing it up a few times, so I'm actually looking for these initial concerns. I do see @charmander 👎 'd this issue, so I'd love to hear from him as to why.

I don't think there's anything about the current F# proposal that would preclude that, but it's not included in the F# proposal itself currently, which is intended to be as minimal as possible. Including some form of placeholders in the F# proposal would probably need to determine its relationship with partial application, which is a problem the Smart Pipeline doesn't have, as its implementation is far more expansive than partial application.

@littledan
Copy link
Member

@mAAdhaTTah Many concerns were expressed in #70 . Making the grammar unambiguous fixes the most basic part, but it seemed like most of these concerns were actually about the behavior of making the scope either shorter (as in this proposal) or longer (as I was originally thinking).

@mAAdhaTTah
Copy link
Collaborator Author

@littledan Ok, I'll take a closer read through there and reach out. Thanks!

@michaelficarra
Copy link
Member

@mAAdhaTTah To be clear, you want arrows to be consistently higher precedence than pipes? That should work. You'd add the pipe production between assignment and sequence.

@js-choi
Copy link
Collaborator

js-choi commented Mar 13, 2018

@michaelficarra: Yeah, it’d probably be something like displacing the async arrow functions’ nonterminals from AssignmentExpression to a new production, replacing them with PipelineExpression. Something like this quick sketch:

AssignmentExpression[In, Yield, Await] :
  PipelineExpression[?In, ?Yield, ?Await]
  [+Yield]YieldExpression[?In, ?Await]
  LeftHandSideExpression[?Yield, ?Await] `=`
    AssignmentExpression[?In, ?Yield, ?Await]
  LeftHandSideExpression[?Yield, ?Await] AssignmentOperator
    AssignmentExpression[?In, ?Yield, ?Await]

PipelineExpression[In, Yield, Await] :
  ArrowFunctionExpression[?In, ?Yield, ?Await]
  PipelineExpression[?In, ?Yield, ?Await] `|>`
    ArrowFunctionExpression[?In, ?Yield, ?Await]
  PipelineExpression[?In, ?Yield, ?Await] `|>
    [ something something here for the bare `await` body notation ]

ArrowFunctionExpression[In, Yield, Await] :
  ConditionalExpression[?In, ?Yield, ?Await]
  ArrowFunction[?In, ?Yield, ?Await]
  AsyncArrowFunction[?In, ?Yield, ?Await]

ConciseBody[In, Yield, Await] :
  [lookahead ≠ `{`] [ I haven’t figured this part out yet ]
  `{` FunctionBody[~Yield, ~Await] `}`

This approach does have important disadvantages, but it might be worth it for the ergonomics of Proposal 1. Don’t yet know.

@littledan
Copy link
Member

cc @zenparsing

@michaelficarra
Copy link
Member

@js-choi That's not putting pipeline lower than assignment. Assuming the ConciseBody production is left unchanged from the current spec, this example is ambiguous:

a => b |> c

Also @mAAdhaTTah, if you do what I described, the downside is pretty steep: you won't be able to assign the result of a pipeline to a variable like this:

a = b |> c

You'd need parens:

a = (b |> c)

But I guess that's okay since it's consistent with sequences:

a = (b, c)

@mAAdhaTTah
Copy link
Collaborator Author

@michaelficarra The suggestion is for this:

a => b |> c

to parse as this:

(a => b) |> c

but requiring parens around assignment too is tough. This is the kind of thing that would be helpful to get dev feedback on thru the babel plugin.

@js-choi
Copy link
Collaborator

js-choi commented Mar 13, 2018

@michaelficarra: Yeah, when I realized that making |> looser than => would mean it’s also looser than =, I gave up for now and left the space in ConciseBody blank. There still might be a way to make |> looser than => but tighter than =, but I don’t yet see it. I’ll try again later.

@littledan
Copy link
Member

l thought the hesitation from
committee members came largely from @michaelficarra , who seemed pretty
receptive to it in the end, e.g.,
#104 (comment)
. I mentioned this at the meeting; I like Michael's suggestion to push
on ahead with un-parenthized arrow functions and get developer
feedback. Overall, I think we should go for this change in the F# branch.

@mAAdhaTTah
Copy link
Collaborator Author

After editing the README for F# Pipelines, I really dislike requiring parens for assignment. It's used like that all over the place, and I actually think that's worse than requiring parens around arrow functions in the pipeline.

I don't know as I want to push out a version in babel that requires parens around assignment and arrow bodies, find out people don't like it, then need to either push out another version of the F# pipeline that reverts this behavior, and push us back to having 4 different proposals again. I think that would be confusing to the community.

Besides me, no one else seems like it's a good idea; just that it's worth getting feedback on. So I'm inclined to leave it with the current behavior if we can't resolve assignment issue. I'd be interested in revisiting this if we get the opposite feedback, e.g. that wrapping arrows in pipelines is a hassle.

That said, I'm going to dig into the precedence rules and see if there's any way of threading this needle.

@jridgewell
Copy link
Member

This would introduce a minor footgun, as this: x => x |> g would parse like this: (x => x) |> g

Could we follow @pygy's suggestion, and add a syntax error for unparenthesized expressions?

Eg:

// syntax error
x => x |> g;

// fine
(x => x) |> g;
x => (x |> g);

@mAAdhaTTah
Copy link
Collaborator Author

@jridgewell Oh, must have missed that. Yes, I like that suggestion.

@pygy
Copy link
Contributor

pygy commented Apr 17, 2018

@jridgewell I don't remember suggesting having an error there.

That one is yours (and I quite like it).

@mAAdhaTTah
Copy link
Collaborator Author

That would explain why I missed it 😀

@jridgewell
Copy link
Member

jridgewell commented Apr 17, 2018

Sorry, they were to separate statements.

Could we follow @pygy's suggestion? Also, let's add a syntax error for the ambiguity.

Maybe: If we follow @pygy's suggestion, let's add a syntax error.

@mAAdhaTTah
Copy link
Collaborator Author

mAAdhaTTah commented Apr 17, 2018

Ah, that makes sense.

This stretches the edges of my spec knowledge, but is it possible to distinguish the arrow on the left vs. right side? e.g. this is valid:

x |> x => x *2 |> x => x + 1

but this isn't

x => x * 2 |> x => x + 1

@jridgewell
Copy link
Member

x |> x => x *2 |> x => x + 1

Oh, that makes my head hurt. I believe we want it to parse as:

x
  |> x => x *2
  |> x => x + 1

If we simplify it into is basest form:

x
  |> x => x
  |> x => x

We wan this to still be legal. But if we write it in shorthand form:

x |> x => x |> x => x

I can't tell where it supposed to parse.

Maybe someone else can help? Or maybe it's just not possible.

@mAAdhaTTah
Copy link
Collaborator Author

mAAdhaTTah commented Apr 17, 2018

Yeah, we want it to parse as:

x
  |> x => x *2
  |> x => x + 1

My question is more whether we can define the spec such that the unparen'd arrow is legal on the RHS but not the LHS, given how the pipeline is typically used.

The shorthand "rule" for mentally parsing arrow functions, as per @pygy, is "the pipeline operator terminates an arrow body."

@michaelficarra
Copy link
Member

@mAAdhaTTah You can't have that and a = b |> c at the same time because a => b = c is already allowed.

@jridgewell
Copy link
Member

What if we split the grammar into two phases? A PipelineStart which cannot have ArrowFunction as a LHS, and PipelineExpression which can?

// I'm not a grammar wizard...

PipelineStart:
  LogicalORExpression
  ArrowFunction |> PipelineStart
  PipelineExpression |> LogicalORExpression

PipelineExpression:
  LogicalORExpression
  PipelineExpression |> LogicalORExpression

So:

  • x |> x => x |> x => x*2 parses as if it were x |> (x => x) |> (x => x*2)
  • x => x |> x => x*2 throw a parse error
    • (x => x) |> x => x*2 should parse, though.

We do something similar with Optional Chaining.

@michaelficarra
Copy link
Member

Where are these grammar productions used (other than within themselves)?

@jridgewell
Copy link
Member

jridgewell commented Apr 18, 2018

At the moment:

ConditionalExpression:
  PipelineExpression
  PipelineExpression ? AssignmentExpression : AssignmentExpression

PipelineExpression:
  LogicalORExpression
  PipelineExpression |> LogicalORExpression

With my proposed changes:

ConditionalExpression:
  PipelineStart
  PipelineStart ? AssignmentExpression : AssignmentExpression

PipelineStart:
  ArrowFunction |> LogicalORExpression // Throw an early error
  PipelineExpression

PipelineExpression:
  LogicalORExpression
  PipelineExpression |> ArrowFunction
  PipelineExpression |> AssignmentExpression

Oh, this would also allow us to ban x |> await, too, because we could only define the |> await helper on PipelineExpression.

@zenparsing
Copy link
Member

I think the F#-style pipeline operator naturally creates the following expectations:

  1. Unparened arrow functions should be allowed as the right operand of |>
  2. |> should not be allowed (outside of parenthesis) within arrow concise bodies to the right of |>

To make that work, you could parameterize the outer expression grammar (similar to how it's parameterized on "in") along the lines of:

(Leaving out async arrows and plenty of other things)

PipelineExpression[In, Yield, Await, Pipe] :
  LogicalOrExpression[?In, ?Yield, ?Await]
  [+Pipe] PipelineExpression[?In, ?Yield, ?Await, +Pipe] |> LogicalOrExpression[?In, ?Yield, ?Await]
  [+Pipe] PipelineExpression[?In, ?Yield, ?Await, +Pipe] |> ArrowFunction[?In, ?Yield, ?Await, ~Pipe]

ArrowFunction[In, Yield, Await, Pipe]:
  ArrowParameters[?Yield, ?Await] [no LineTerminator here] => ConciseBody[?In, ?Pipe]

ConciseBody[In, Pipe]:
  [lookahead ≠ {] AssignmentExpression[?In, ~Yield, ~Await, ?Pipe]
  { FunctionBody [~Yield, ~Await] }

I think the question becomes: do we want to introduce an operator that creates expectations that conflict slightly with the language's existing operator precedence rules (in certain contexts)? Is it worth it?

@charmander
Copy link

I think the F#-style pipeline operator naturally creates the following expectations:

  1. Unparened arrow functions should be allowed as the right operand of |>
  2. |> should not be allowed (outside of parenthesis) within arrow concise bodies to the right of |> [sic]

For what it’s worth, I still think it creates the opposite expectations and value the unambiguous nature of parenthesized arrow function operands.

@mAAdhaTTah
Copy link
Collaborator Author

Oh, this would also allow us to ban x |> await, too, because we could only define the |> await helper on PipelineExpression.

Wait, why do we want to ban x |> await?

@jridgewell
Copy link
Member

Because it should be written as await x. It was just a side thought, nothing that we actually need to do.

@littledan
Copy link
Member

We spent a while debating different syntaxes for await in pipeline. Now, we have two active proposals that pursue different paths on this question. Let's work through this by making progress on the Babel implementations and user studied based on that, to add to the theorizing which has happened in this and other threads.

@mAAdhaTTah
Copy link
Collaborator Author

The usage of await is orthogonal to the main question of this issue. The issue I have, related to babel, is thus (described above):

I don't know as I want to push out a version in babel that requires parens around assignment and arrow bodies, find out people don't like it, then need to either push out another version of the F# pipeline that reverts this behavior, and push us back to having 4 different proposals again. I think that would be confusing to the community.

so I actually don't know if getting community feedback thru the babel plugin is the best route here.

@littledan
Copy link
Member

Is this about Babel stability guarantees? Maybe another transpiler could be a better platform for development?

I think we already have plenty of evidence on these bug threads that many people would be disappointed if they had to use parens; the no-parens effort seems like worth a shot to me, though it takes some work.

@mAAdhaTTah
Copy link
Collaborator Author

Both babel stability guarantees as well as community expectations.

I'm concerned about going out and saying "F# is like this, with parens here and here but not here", have people dislike it, and then have to go back and say "no, actually, now it's reversed with parens here, but not here and here." I think it'll muddy the signals from the community; we already had this problem when we were looking at 5 proposals, not knowing what the operator is. I think potentially ending up with multiple variants of F# proposal would be bad.

Given that, I'd prefer to commit to one and stick with it, and I find myself hesitating to commit to unparen'd arrow functions, especially given the fact that we cannot eliminate requiring parens for assignment.


I have a couple questions though:

  1. Are there other places in the language where you cannot use arrow functions without parenthesizing them?
  2. Besides sequence expressions, are there other expressions in the language that require parens for assignment?

Trying to get the "expectations" question posed by @zenparsing & @charmander.

@littledan
Copy link
Member

About community expectations of stability: I was hoping that whichever way we decide on this question, we could communicate clearly to this is a very early experiment, the community that the F# and smart proposals are two points on a big spectrum, and we will use the experiences from this stage to inform the design of the following proposal. We are still tweaking Stage 3 proposals, so we will definitely be open to tweaking these alternatives as they progress.

@mAAdhaTTah
Copy link
Collaborator Author

mAAdhaTTah commented Apr 21, 2018

@jridgewell Going out on a limb:

If we can spec PipelineStart separately from PipelineExpression, could we allow an arrow function at the head and make its precedence rules different from arrow functions in the body? Or would be that be terrible?

It would be thus:

const a = x => x
  |> x => x + 2
  |> double
  |> console.log
// parses as (with all the parens)
const a = x => (x
  |> (x => x + 2)
  |> double
  |> console.log)

Is this possible?

@jridgewell
Copy link
Member

🤷‍♂️.

@mAAdhaTTah
Copy link
Collaborator Author

@michaelficarra Thoughts? This possible?

If it is, I think that would most closely match expectations. That's essentially what the babel plugin did (before we landed babel/babel#7818 to bring it in line w/ current minimal).

@littledan
Copy link
Member

littledan commented Jun 26, 2018

@jridgewell Re: #104 (comment) , I'm a bit confused about what problem this suggestion is trying to solve. Are you trying to find a grammar that will work somehow and correspond to the expectation that you have non-parenthized arrow functions?

If that's the goal, do you think @mAAdhaTTah 's proposal at valtech-nyc#3 would work? If I understand correctly, this PR would have the semantics of consistently choosing the narrow parenthization.

EDIT: Oh, I see, that other PR would require too many parentheses in a common assignment case. In that case, I wonder if @zenparsing 's grammar in #104 (comment) would meet these full goals in a somewhat simpler way.

@mAAdhaTTah
Copy link
Collaborator Author

@littledan I think his proposal essentially matches what we have at valtech-nyc#3. The question is more whether we can extend it as mentioned in #104 (comment), as I think that's probably most closely aligned with how people expect it to work/

@tabatkins
Copy link
Collaborator

Closing this issue, as the proposal has advanced to stage 2 with Hack-style syntax. |> is specced as having the same precedence as =>, and there's very little reason to put arrow functions directly in pipe bodies now.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 11, 2021
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

9 participants