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

Placeholder scope/visibility #262

Open
dy opened this issue Feb 11, 2022 · 16 comments
Open

Placeholder scope/visibility #262

dy opened this issue Feb 11, 2022 · 16 comments
Labels
question Further information is requested

Comments

@dy
Copy link

dy commented Feb 11, 2022

Following #261 − what are the boundaries for placeholder? (sorry if question was asked.)

// is it possible to nest placeholder within a function?
source |> a => # + a |> output(#)

// is it possible to define a function outside of pipeline to allow reusing?
const add= a => # + a
source |> add(a) |> output(#)

// is placeholder available from async context?
source |> setTimeout(() => # + 1) 

// will a function defined in pipeline context retain reference to placeholder?
let add = source |> a => # + a
add(1)
@ljharb
Copy link
Member

ljharb commented Feb 11, 2022

Yes, no, yes, yes.

@dy
Copy link
Author

dy commented Feb 11, 2022

// is placeholder scope defined by parens?
source |> a + # |> b + #
source |> (a + # |> b) + #
source |> a + (# |> b + #)

@ljharb
Copy link
Member

ljharb commented Feb 11, 2022

Each pipeline step needs a placeholder, and i would assume that a pipeline, being an expression, would be entirely contained by parens - meaning, your third example there seems like it might not parse.

@dy
Copy link
Author

dy commented Feb 11, 2022

Potential userland scenario: to make functions reusable, fake stack can be organized in underscore chain fashion:

let arg
const _ = a => arg = a,
    add = a => arg += a
    
_(source) |> add(a) |> output(#)

@js-choi js-choi added the question Further information is requested label Feb 11, 2022
@dy
Copy link
Author

dy commented Feb 11, 2022

would be entirely contained by parens - meaning, your third example there seems like it might not parse.

Does it mean parens are limitations to placeholder visibility? In that case boundaries should be more clearly defined...

// if that's not valid
source |> a + (# |> b + #) 

// then what about
source |> a + (a => # |> b + a)(#)
source |> a + (a => (# |> b + a))(#)
value |> foo(() => class { constructor() { return addEvent('x', function () { return () => [await x`${ # }`]})} } )

@ljharb
Copy link
Member

ljharb commented Feb 11, 2022

No, it’s saying that the inner pipeline could only parse in its entirety because it’s inside parens. Parens don’t affect placeholder visibility.

@ljharb
Copy link
Member

ljharb commented Feb 11, 2022

although, it also might parse so the LHS of the inner pipeline is the outer pipeline’s placeholder?

@dy
Copy link
Author

dy commented Feb 11, 2022

LHS of the inner pipeline is the outer pipeline’s placeholder

That's what I'd guess

@js-choi
Copy link
Collaborator

js-choi commented Feb 11, 2022

Keep in mind the following paragraphs from the explainer’s description:

A pipe body must use its topic value at least once. For example, value |> foo + 1 is invalid syntax, because its body does not contain a topic reference. This design is because omission of the topic reference from a pipe expression’s body is almost certainly an accidental programmer error.

Likewise, a topic reference must be contained in a pipe body. Using a topic reference outside of a pipe body is also invalid syntax.

To prevent confusing grouping, it is invalid syntax to use other operators that have the same similar precedence (the arrow =>, the ternary conditional operator ? :, the assignment operators, and the yield operator) as a pipe head or body. When using |> with these operators, we must use parentheses to explicitly indicate what grouping is correct. For example, a |> b ? % : c |> %.d is invalid syntax; it should be corrected to either a |> (b ? % : c) |> %.d or a |> (b ? % : c |> %.d).

This is formally defined in this early-errors section of the specification.

So the lines in each of the following code blocks are equivalent to one another.

source |> a => # + a |> output(#)
SyntaxError: Pipe body `a => # + a` is unparenthesized arrow function. Arrow function must be parenthesized.
source |> (a => # + a) |> output(#)
output(a => source + a)
source |> (a => # + a |> output(#))
a => output(source + a)
const add= a => # + a
SyntaxError: Unbound topic `#` was found in `# + a`. Topics may only be used within pipe bodies.
source |> add(a) |> output(#)
SyntaxError: Topic was not used within pipe body `add(a)`.
source |> setTimeout(() => # + 1) 
setTimeout(() => source + 1)
_(source) |> add(a) |> output()
SyntaxError: Topic was not used within pipe body `add(a)`.
let add = source |> a => # + a;
SyntaxError: Pipe body `a => # + a` is unparenthesized arrow function. Arrow function must be parenthesized.
let add = source |> (a => #) + a;
let add = (a => source) + a;
let add = source |> (a => # + a);
let add = (a => source + a);
source |> a + # |> b + #
source |> (a + #) |> (b + #)
b + (a + source)
source |> (a + # |> b) + #
SyntaxError: Topic was not used within pipe body `b`.
source |> a + (# |> b + #)
source |> a + (b + #)
a + (b + source)
source |> a + (# |> b + #)
source |> (a + #) |> (b + #)
b + (a + source)
source |> a + (a => #.x |> b + a)(#)
source |> a + (a => (#.x |> b + a))(#)
SyntaxError: Topic was not used within pipe body `b + a`.
source |> a + (a => #.x |> b + a + #)(#)
source |> a + (a => b + a + (#.x))(#)
source |> a + (a => (#.x |> b + a + #))(#)
source |> a + (a => (b + a + (#.x)))(#)
a + (a => b + a + source.x)(source)
a + (a => (b + a + source.x))(source)
source |> a + (a => (# |> b + a))(#)
SyntaxError: Topic was not used within pipe body `b + a`.
value |> foo(() => class { constructor() { return addEvent('x', function () { return () => [await x`${ # }`]})} } )
foo(() => class { constructor() { return addEvent('x', function () { return () => [await x`${ value }`]})} } )

In general, I would encourage developers to keep their pipe expressions’ bodies as small, simple, and readable as possible. In particular, I would avoid nesting pipe expressions within parentheses within pipe expressions: the purpose of pipes is to untangle nested parentheses, not to create more.

@dy
Copy link
Author

dy commented Feb 11, 2022

it is invalid syntax to use other operators that have the same precedence (the arrow =>, the ternary conditional operator ? :

Ternary has higher precedence than assignment, I guess there should not be a problem with a |> b ? % : c.

@js-choi
Copy link
Collaborator

js-choi commented Feb 11, 2022

Ternary has higher precedence than assignment, I guess there should not be a problem with a |> b ? % : c.

I think this might be an error with MDN’s table. The actual language specification defines them on the same precedence level: observe how an AssignmentExpression may be a ConditionalExpression, which in turn may be a ShortCircuitExpression ? AssignmentExpression : AssignmentExpression. I will try to open a pull request to correct this table later.

After talking with some others, I’ve been reminded that talking about “precedence” with ternary operators (rather than only with binary/unary operators) is not very meaningful (thanks @bakkot). So we should rather reword the proposal explainer’s statement to say “similar precedence” rather than the “same precedence”.

The proposal specification is still correct; it already distinguishes “identical precedence” from the conditional operator: “A PipeBody must not be an unparenthesized expression that is formed from any other operator with identical precedence (such as YieldExpression or ArrowFunction) or from the conditional operator.”

@dy

This comment was marked as off-topic.

@js-choi
Copy link
Collaborator

js-choi commented Feb 11, 2022

This is a little off topic, but the weirdness here is that = is not really a binary operator in the usual sense. It is a weird operator: it is kind of like a prefix operator that operates on its RHS. Its LHS is not an ordinary expression but rather more like a parameter list.

To the extent that ternary operators can even be considered to have precedence, it binds less tightly than yield and = because yield a ? b : c is yield (a ? b : c) and x = a ? b : c is x = (a ? b : c).

@dy

This comment was marked as off-topic.

@dy
Copy link
Author

dy commented Feb 11, 2022

Speaking of precedence, to clarify:

a ? b : c |> #
// is equivalent to
(a ? b : c) |> #
// and not
a ? b : (c |> #)

// or still 
a ? b |> # : c |> # 
// as
a ( b |> # ) : (c |> #) 
// would have more sense?

@js-choi
Copy link
Collaborator

js-choi commented Feb 11, 2022

I think we intend to ban a ? b : c |> # without parentheses and to force the developer to specify between (a ? b : c) |> # or a ? b : (c |> #). I forgot to put this particular error in the spec, though, and I should add it. We should also clarify this in the explainer.

js-choi added a commit that referenced this issue Feb 11, 2022
Prohibit ambiguous conditional expression ending with pipe expression,
e.g., `a ? b : c |> d(%) |> e(%)`.
See #262 (comment).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants