Closed rbuckton closed 3 years ago
Will it be OK to use a token containing this
for a construct which is not referencing the current lexical this
value?
If optional chaining is switching to ??.
syntax, why not just use ?.
for this case?
I'd rather stay away from ?.
for this case as its too visually similar to ??.
and has the same meaning in other languages.
As far as using this
, I could use any identifier or keyword, even ?$
or ?_
, but I feel that ?this
is clearer as to its purpose and is much more descriptive.
I don't understand the similarity concern. I could see avoiding ?
as it is similar to ??
, but the idea of the ??.
proposal is that ??
is considered a token by itself, which can compose with .
, [
and (
. If partial application sticks with using ?
, it should be because ?
is a distinct enough token from ??
that it is not confusing.
I agree that using another token like ?$
or ?_
would be more confusing.
Besides, I'm likely to change the grammar above to include a single-step optional chain as well, e.g.:
?this??.prop()
?this??[expr]()
Yeah, that's a lot of ?
s there, but its arguably better than ???.prop()
, etc. Things improve as well if syntax highlighters are updated to treat ?this
as a single token as a keyword.
It could also be something as simple as ?value
.
Although, I'm less likely to consider adding support for optional chaining into this proposal if we eventually have a syntax for optional pipelines (e.g. ?>
).
Another option would be to change the placeholder to not be ?
so that there is no ambiguity with ??
. For example, if the placeholder is ^^
, then ^^??.f()
might not be as confusing. It's a bit of ASCII soup, but that's sort of inherent in these two proposals, as they are all about adding tokens as shorthand.
I don't particularly like ^^
as a token for this purpose, it feels arbitrary in this context compared to ?
which could be visually interpreted as a placeholder for something.
I suppose that if optional chaining is using ??.
, ??[
and ??(
for chaining in an infix position, then we could leverage ?.
, ?[
and ?(
for partial application since it's in a prefix position. In all of those cases the usage would have restrictions that it's only valid as part of a call, so ?.x
and ?[x]
would be invalid, but ?.x()
and ?[x]()
would be valid (and possibly even ?()
). With a few tweaks, the following could all have a valid meaning:
// partially apply property access call
?.x() // _a => _a.x()
?.x(?) // (_a, _0) => _a.x(_0)
// partially apply element access call
?[expr]() // $$temp = expr, _a => _a[$$temp]()
?[expr](?) // $$temp = expr, (_a, _0) => _a[$$temp](_0)
?[?]() // (_a, _b) => _a[_b]()
?[?](?) // (_a, _b, _0) => _a[_b](_0)
a[?]() // $$temp = a, _b => $$temp[_b]()
a[?](?) // $$temp = a, (_b, _0) => $$temp[_b](_0)
// partially apply function call
?() // _a => _a()
?(?) // (_a, _0 => _a(_0)
@rbuckton If it was to be implemented then I would expect the following syntaxes to also work or at least be considered in the future (using #
as the placeholder to make it more clear):
#.prop === (x) => x.prop // property access
#?.prop === (x) => x?.prop // optional property access
#(arg) === (x) => x(arg) // call
#?.(arg) === (x) => x?.(arg) // optional call
#.method() === (x) => x.method() // method call (this proposal)
#?.method() === (x) => x?.method() // optional method call
#.method?.() === (x) => x.method?.() // method with optional call
// etc
So using ?
as the placeholder would result in ?.prop
vs ??.prop
confusion.
I don't know if it's feasible, but another option is to not have any symbol at all:
const friendsList =
getData('users')
|> JSON.parse
|> .filter(.friends.includes(currentUser))
|> .map(.name)
|> unique
|> .join(', ')
// same as
const friendList =
getData('users')
|> JSON.parse
|> users => users.filter(user => user.friends.includes(currentUser))
|> users => users.map(user => user.name)
|> unique
|> users => users.join(', ')
@phaux that wouldn't be feasible, because dot access and bracket access must both work, and [Symbol.iterator]
, ['abc']
, etc, all look like they're an array. Also, .1.toString
is a function value already.
.1
wouldn't be an issue since 1
isn't a valid IdentifierStart. Prefix-dot is still an option for element access as .[x]
given the precedent set by o?.[x]
.
@rbuckton you can't put an expression in the pipeline?
According to latest node.js:
> [].map(.1)
Thrown:
TypeError: 0.1 is not a function
at Array.map (<anonymous>)
but
> [].map(.a)
Thrown:
[].map(.a)
^
SyntaxError: Unexpected token '.'
so it could work as long as the property names aren't numeric.
and for computed property names it would be .[x]
which is consistent with optional chaining which also requires an extra dot.
> [].map(.["x"])
Thrown:
[].map(.["x"])
^
SyntaxError: Unexpected token '.'
I'm not entirely sure if it's worth treating dot+numbers versus dot+identifier so much differently. I'm just throwing out ideas.
leading .
or leading ?
will let who not use semicolon make more mistake, and make compiler implement more difficult.
Given the syntax and semantics introduced in #49, and the advancement of Hack-style pipelines to Stage 2, I no longer believe introducing a placeholder for the receiver binding is an avenue to pursue.
This suggestion was initially driven by a demand to support piping a callee in F#-style pipelines:
[1, 2, 3] |> ?.map(x => x + 1);
However, Hack-style pipelines do not require this capability, as [1, 2, 3] |> ^.map(x => x + 1)
does not involve partial application.
In #49, we introduced the prefix token ~
to indicate partial application to satisfy a constraint to avoid "the garden path". This token is inserted between the callee and the argument list to make the following semantics very explicit:
Function.prototype.bind
). All non-placeholder arguments are eagerly evaluated and fixed at the time the partial application expression is evaluated.Both the smart-mix and Hack-style pipeline proposals were considering a prefix token like +>
that came before the callee:
+> f(?)
However, in both cases these expressions are lazily evaluated and semantically the same as x => f(x)
. While this would allow for partial expressions (i.e., +> ? + ?
), arrow functions are already a perfectly viable (and more flexible) solution for lazy evaluation.
A prefix token that comes before the callee is not conducive to eager evaluation, as it can introduce confusion as to which part of a more complex expression is to be partially applied:
+> o.f().g(?)
To suit eager evaluation, the above would necessarily be a syntax error since the first call expression we encounter is o.f()
, which leaves a dangling .g(?)
that is not partially applied.
We chose ~(
as it mitigates this confusion. It becomes very clear which argument list is partially applied:
o.f().g~(?)
This change also means that only arguments can be partially applied, since the prefix applies to the argument list and not the callee. Since the callee is not an argument, it cannot be partially applied. Instead, you have two alternatives if you need to "partially apply" the callee: arrow functions and utility functions.
As mentioned above, arrow functions are lazily evaluated. They introduce a closure over the outer environment record, which means that they will observe state changes to closed-over variable bindings. In addition, the body of an arrow function is repeatedly evaluated each time it is called, meaning that any side effects within the body of the arrow can be observed. If your code is structured in such a way that there are no mutations to closed over variables and the arrow body does not contain side effects, then an arrow function is a perfectly acceptable solution to providing a partially-applied callee.
If eager-evaluation semantics are still necessary, however, its fairly easy to write utility functions that can support partial application of a callee:
const call = (callee, ...args) => callee(...args);
const invoke = (receiver, key, ...args) => receiver[key](...args);
const callWith1And2 = call~(?, 1, 2);
const invokeSayHello = invoke~(?, "sayHello");
So in a Hack pipeline, ^o.f~(a, ?, c)
would not eagerly cache o
as the receiver for the call?
If eager-evaluation semantics are still necessary, however, its fairly easy to write utility functions that can support partial application of a callee:
If such helpers seem useful, especially with respect to partial application, I suggest you file an issue on https://github.com/js-choi/proposal-function-helpers for their inclusion (although call
would need a different name..)
So in a Hack pipeline,
^o.f~(a, ?, c)
would not eagerly cacheo
as the receiver for the call?
Did you mean ^.f~(a, ?, c)
? If so, the result of that expression would be a partially applied function whose receiver is whatever ^
was at the time the pipeline is evaluated, and whose callee is whatever ^.f
was at the time it was evaluated.
Pipelines are still evaluated left-to-right, so in source |> ^.f~()
, the ^.f~()
portion won't be evaluated until after source
and will therefore have a valid expression on which to operate.
oops, yes, i did mean ^.f
- and great, thank you.
For example:
const bob = {
name: "Bob",
sayHelloTo(name) {
console.log(`Hello ${name}, I'm ${this.name}!`);
}
};
const sayHello = bob |> ^.sayHelloTo~(?);
sayHello("Alice"); // prints: Hello Alice, I'm Bob!
This proposal extends CallExpression to allow
?this.prop()
or?this[expr]()
as a way to define a placeholder argument for a partially applied CallMemberExpression. Both?this
and?
could be combined in the same expression, in which case the?this
placeholder argument will be the first argument in the partially applied function result.Syntax
Grammar
Examples
The following show approximate syntactic transformations that emulate the proposed semantics:
It is not possible to reference
?this
on its own in an argument list.Alignment with the pipeline operator (
|>
)When combined with the pipeline operator proposal, this feature could allow the following (based on original example in https://github.com/tc39/proposal-pipeline-operator/wiki):
Alignment with other proposals
This syntax aligns with the possible future syntax for positional placeholders (e.g.
?0
,?1
) as proposed in #5. If both proposals are adopted, then when using?this
all positional placeholders are offset by 1 with respect to their actual ordinal position in the argument list (similar toFunction.prototype.call
). For example: