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

Why no use .? as token? #61

Closed
michaeljota opened this issue Apr 24, 2018 · 37 comments
Closed

Why no use .? as token? #61

michaeljota opened this issue Apr 24, 2018 · 37 comments
Labels
alternative syntax past ideas and discussions about alternative syntaxes

Comments

@michaeljota
Copy link

michaeljota commented Apr 24, 2018

Hi,

I don't think I have enough experience about tokens in Javascript, but the current token could have some backwards compatibility issues, as far I can tell. In the spec said it at least.

Maybe this is not the reason this haven't go to stage two, but I would like to see this implemented. I understand that ?. could be confused with a ternary operator using a decimal number as the if clause.

But, can .? can be use instead? I know that ?. is used in other languages as well, but JS is special, we all know that.

Another thing about this, is that you can use properties, and function calls in the same way, so there is nothing unexpected here.

const foo = bar.?foo;
const res = bar.?fun();
const prop = bar.?[prop];

Like I say, I don't know that much about tokens, maybe this was in the table, but I just wanted to say it.

Thanks you.

@jridgewell
Copy link
Member

jridgewell commented Apr 24, 2018

1.?foo would have to be a syntax error, though, since 1.?foo : bar is already valid syntax. This shouldn't hurt much, since [[NUMBER]] could never by nullish.

I've talked with @gisenberg and and @ljharb about this once, and the objection is it's not symmetric between .?, .?( and .?[.

But, I'd still prefer this option over the current proposal (?., ?.(, and ?.[).

@ljharb
Copy link
Member

ljharb commented Apr 24, 2018

Indeed - the syntax collision with numbers, and the lack of symmetry, are a problem.

@michaeljota
Copy link
Author

michaeljota commented Apr 25, 2018

1.?foo would have to be a syntax error, though, since 1.?foo : bar is already valid syntax.

So both 1.?foo and foo.?.1 would be a syntax error.

But, I'd still prefer this option over the current proposal (?., ?.(, and ?.[).

I would too, because of consistency. I understand why ?.( have to be that way, I just don't like it. xd

@michaeljota
Copy link
Author

Another though about this.

Currently the optional chaining for functions is foo?.bar?.() but, this proposal would only check if this value is null or undefined. My point is, this won't check that you can actually can that function before try to call it, right? So why even bother to make a token for optional calling a function that you can't actually check if this is a callable value?

@jridgewell
Copy link
Member

See #2 for "is it callable" discussion.

And see #59 for dropping call syntax.

@rbuckton
Copy link

Indeed - the syntax collision with numbers, and the lack of symmetry, are a problem.

1.toString() is a syntax error. 1.?toString() doesn't necessarily need to be a syntax error, since while its a minor footgun because its already legal syntax its also an unlikely case as mentioned above.

One option could be a.?b and a[?b]. I'm leaving out optional call in this syntax example since it would conflict with partial application and I'm becoming less convinced optional call is necessary (since you can do f.?call(undefined) instead).

@aminpaks
Copy link

aminpaks commented Jun 4, 2018

I don't understand how this proposal is supposed to cover this case then:

var a = {1: 'value'};
console.log(a?.1:0); // => will be 0.1 instead of `value` !?

Am I missing something?

@ljharb
Copy link
Member

ljharb commented Jun 4, 2018

@aminpaks you can't do a.1 - it has to be a[1] - so a?.1 is unambiguously the start of a ternary expression. You'd need a?[1] to conditionally extract a numeric literal property.

@aminpaks
Copy link

aminpaks commented Jun 4, 2018

@aminpaks you can't do a.1 - it has to be a[1] - so a?.1 is unambiguously the start of a ternary expression. You'd need a?[1] to conditionally extract a numeric literal property.

So you're saying it has to be a?.[1] as your proposal?

@ljharb
Copy link
Member

ljharb commented Jun 4, 2018

@aminpaks yes, that's how you'd extract a numeric property with optional chaining.

@aminpaks
Copy link

aminpaks commented Jun 4, 2018

I just realized there is different cases, that's why I got confused.

I'd highly recommend to include more examples to clarify.

var a = {
  b: () => 'value-b',
  c: ['value-c', undefined, null],
  y: null,
};

// optional-chaining
a.b?.(); // => 'value-b'

a.c?.[0]; // => 'value-c'
a.c?.[1]; // => undefined
a.c?.[2]; // => null
a.c?.[3]?.(); // => undefined

a?.y; // => null

a?.z; // => undefined

At the end personally think mixing the array?.[idx] with obj?.property is not trivial.

@claudepache
Copy link
Collaborator

claudepache commented Jun 4, 2018

@aminpaks

I'd highly recommend to include more examples to clarify.

The explainer gives already the three cases:

obj?.prop       // optional static property access
obj?.[expr]     // optional dynamic property access
func?.(...args) // optional function or method call

This is easily adaptable to whatever syntax we’ll pick

obj.?prop       // optional static property access
obj.?[expr]     // optional dynamic property access
func.?(...args) // optional function or method call

or

obj??.prop       // optional static property access
obj??[expr]      // optional dynamic property access
func??(...args)  // optional function or method call

or

obj?&.prop       // optional static property access
obj?&[expr]      // optional dynamic property access
func?&(...args)  // optional function or method call

If something is not clear, please, open a new issue.

@JoshMcCullough
Copy link

JoshMcCullough commented Jun 8, 2018

IMO the ? belongs before the . because in person.address, the question is "is person null? if not, grab a property named 'address'" hence person?.address.

@aminpaks
Copy link

aminpaks commented Jun 8, 2018

IMO the ? belongs before the . because in person.address, the question is "is person null? if not, grab a property named 'address'" hence person?.address.

My concern is about readability in the following:

obj?.myCallback?.(); // Invokes `myCallback` property if it's not falsy
obj?.myArrayProperty?.[2]; // Returns third element of `myArrayProperty` array if it's not falsy
obj?.myArrayProperty?.[3]?.(); // Invokes forth element of `myArrayProperty` array if it's not falsy

The other options are even worth:

obj??.myCallback??();
obj??.myArrayProperty??[2];
obj??.myArrayProperty??[3]??();
obj.?myCallback.?();
obj.?myArrayProperty.?[2];
obj.?myArrayProperty.?[3].?();

@tenbits
Copy link

tenbits commented Jun 8, 2018

"is person null? if not, grab a property named 'address'"

@JoshMcCullough Or you can interpret it this way.

  1. We have the value of person on the stack (e.g. it was as null resolved)
  2. Now, get the value of the conditional property address, which in normal case would cause the exception, but the exception is not thrown, as the property accessor is optional.

So instead of "get the value of the property address" (person.address) it would be "try to get the value of the property address" (person.?address).

@JoshMcCullough
Copy link

@tenbits yes, but address is not the potential null thing we are protecting ourselves from, person is. The ? being on the right side of the period, to me, implies that the operator affects the child property, not the parent.

@michaeljota
Copy link
Author

I understand there should be motive, and a reasoning behind the token. But I have to say I did not rationalized this much in the meaning point of view.

I do, however, think that we process the token as one, and either its .? or ?., the token as a whole means if the previous thing is not null, or undefined, then access the property.

I don't think that when we are programming, we would see this a two characters, but I understand the point. Maybe that's why dislike the obj?.[key] that much.

@tenbits
Copy link

tenbits commented Jun 12, 2018

@JoshMcCullough, again, it depends, I would say, we protect ourself not from actual values neither persons, nor address, but from the property accessor .propertyName, so that this is optional, and being executed on null context doesn't throws exception. But that's how I see this, and I can be wrong in context of this proposal.

@JoshMcCullough
Copy link

I'd suggest if you were to write one of these if (something) then (name) things in English, it would read "if something exists then get name".

if (something exists) then (get name)
    ^-------^ ^----^  ^--^  ^------^
      |         |             |
   subject  conditional    member access
      |         |             |  
 "something"   "?"         ".name"

By splitting . from name as in the OP here, you are splitting the member accessor and it doesn't read correctly to me.

In that case, why not replace the member accessor entirely? Instead of something?.name or something.?name, just drop the .: something?name. (I realize it doesn't read well, visually.)

Or, what if we never care about nullability of any child property?

let firstName = ? person.driversLicense.firstName;

Lead with a ? to indicate that null-checking should occur prior to each member access in the rest of the line or segment. I'm not necessarily suggesting this alternative, but just trying to point out that the position of the ? is meaningful.

@aminpaks
Copy link

aminpaks commented Jun 12, 2018

@JoshMcCullough there has been many discussion about what you mentioned. Apparently there is some technical difficulties in the ways that prevent the parser to distinguish a ternary if true ? here : otherwise that forces this proposal to get a bit of controversial.

Or, what if we never care about nullability of any child property?

I believe this has been mentioned and discussed too, and as a developer we want to have the power to check the nullability of each level as this can introduce bugs...

@tenbits
Copy link

tenbits commented Jun 12, 2018

I see it this way:

(get something) then (try get name)
     ^-------^        ^-----^ ^--^
      |                |         |
    subject         conditional  member access
      |                |         |  
   "something"   ".?, ?., wtv"   "name"

When you consider the polish notation: ?. something name.

1.  for each token in the prefix expression:
2.    if token is an operator:
3.      push token onto the operator stack
4.      pending_operand ← False
5.    else if token is an operand:
6.      operand ← token
7.      if pending_operand is True:
8.        while the operand stack is not empty:
9.          operand_1 ← pop from the operand stack
10.         operator ← pop from the operator stack
11.         operand ← evaluate operator with operand_1 and operand
12.     push operand onto the operand stack
13.     pending_operand ← True
14. result ← pop from the operand stack

✂ Prefix_evaluation_algorithm

On line 11 after operand(reference accessor something) is resolved, the operator logic desides what to do next: should it continue (by resolving the property in our case), or should it exit with e.g. null value.

btw. As I understand from this proposal, this should be one single operator ?., ??., &&. etc. So you can't split it for something? .name.

@michaeljota
Copy link
Author

As I understand from this proposal, this should be one single operator ?., ??., &&. etc. So you can't split it for something? .name.

This is it. Is a single token, or operator, compose by two characters, but it will be one, with one single meaning that can't take apart.

@ljharb
Copy link
Member

ljharb commented Jun 12, 2018

Like == or ++ or += etc.

@aminpaks
Copy link

This is it. Is a single token, or operator, compose by two characters, but it will be one, with one single meaning that can't take apart.

Depends how to translate it

obj?.anotherOb?.[1];
obj?.someArray?.[0];
obj?.someFunction?.();

// Technically speaking should NOT be this so '?.' counts as one token?
obj?..anotherObj?.[1];

@michaeljota
Copy link
Author

michaeljota commented Jun 12, 2018

@aminpaks I don't really get your point. The current proposal is exactly what you wrote.

EDIT: Oh! I see it... The .. Thing, is it? The mean of the token is "if the previous thing is not null, or undefined, then access the property.", as been said. So a ?.. would be redundant.

@aminpaks
Copy link

aminpaks commented Jun 12, 2018

I don't really get your point. The current proposal is exactly what you wrote.

Not exactly:

obj?..aProperty; // <- two dots
obj?.aProperty; // this proposal
// If we go with a different token then yes you're right
obj??.aProperty;
obj?&.aProperty;

@robbiespeed
Copy link

this.?bar would certainly be more consistent with private fields syntax this.#bar, though they are in no way functionally similar. To me consistency in the tokens outweighs visual symmetry (?.[ ?.( vs .?[ .?().

I suppose if optional chaining is to be extended to work with private fields that would look something like this.?#bar.

@michaeljota
Copy link
Author

I understand that in JS this is actually an argument, but why should this be checked for undefined? It's an interesting use case, but if you are accessing a private property inside an instance, then you should have a safe go to this. At least, if you are doing things with good practice.

@ljharb
Copy link
Member

ljharb commented Jun 27, 2018

this is determined at invocation time, so it absolutely needs to be checked as if it's any other object.

@michaeljota
Copy link
Author

michaeljota commented Jun 27, 2018

But the given use case is to access a private member, so the execution is most likely to happen inside the instance of a class, so accessing this should be safe, unless you are doing something that you shouldn't.

I mean, in order to have access to a private member, this have to be inside a class instance, haven't it?

@ljharb
Copy link
Member

ljharb commented Jun 27, 2018

@michaeljota no. Foo.prototype.bar.call(wrongObject)

@robbiespeed
Copy link

My example was purely to show similarities with private access token, could have just as easily written foo instead of this.

Currently proposed ?. which seems counter to .#. Dot first for both would atleast seem more consistent.

@michaeljota
Copy link
Author

@ljharb Oh! Ok. yes. Good example. Still, I don't think call is a good practice, but sometimes we have to use it.

@robbiespeed I get your point now. It's just that is a good use case, in the readme there is no mention about usage with private properties.

this.?#b;
this?.#b;
this.#b.?a;
this.#b?.a;

I actually like .?# 🌮

@JoshMcCullough
Copy link

Depends how to translate it

obj?.anotherOb?.[1];
obj?.someArray?.[0];
obj?.someFunction?.();

Array access would simply be obj?.anotherObj?[0] -- you don't use a . to access an array's members like array[0], so why would you add a dot in this use case?

@michaeljota
Copy link
Author

@JoshMcCullough That's been discussed many times. They are introducing a new token ?. as a whole, they are not two different tokens, but one only, to do the optional chaining.

@claudepache
Copy link
Collaborator

Array access would simply be obj?.anotherObj?[0] -- you don't use a . to access an array's members like array[0], so why would you add a dot in this use case?

See the FAQ, first item.

@JoshMcCullough
Copy link

@claudepache / @michaeljota -- Thanks. Although it's ugly IMO, I understand why it's necessary in this case.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
alternative syntax past ideas and discussions about alternative syntaxes
Projects
None yet
Development

No branches or pull requests

9 participants