-
Notifications
You must be signed in to change notification settings - Fork 75
(null)?.b should evaluate to null, not undefined #69
Comments
While a good edge-case to recognize, I think the counterpoint would be that you can actually give information about I could also respond "There is no car" (the equivalent of returning |
@0x24a537r9 adding to what you said, once the property is more than one level nested, e.g. in the question "what is the color of the car's seats", the answer |
Eh, using undefined suffers the same issue. If you ask "What color are the car seats?" and receive undefined, it's equally unclear whether that's because there's no car or the car has no seats. Both approaches lose information and can be ambiguous--it's just a choice of which cases you want to prioritize. |
Agree with @lehni . As we know, in many cases Further, most APIs may have type |
I think if you need that certainty, you’d be unable to use this operator regardless, so that’s not an applicable argument. |
I think @hax nailed it. It is a matter of clarity and correctness. Shoving null through an optional chain is quite confusing. The simple fact of the matter is, |
I don't think this is an apt comparison—short-circuiting is based on a falsy check, not a nullish check. 🤔 Note that in |
We need another operator which returns the last property in the chain which is not |
We definitely don't need another operator - also |
@rkirsling It is an apt comparison for Also, the result of That said, I think, unfortunately, both sides are talking past each other because they're not able to see that the other group is expecting fundamentally different behavior, and neither is inherently wrong--just different expectations. One group defines @rkirsling and @lehni, I'm not saying you're wrong, but as someone who was at one point on the other side of this interpretation (and now on the side of Those that believe that |
D'oh, I guess that was wishful thinking then. (The specific cases I had in mind were Lodash and Ember.) I think this operator is very desirable either way, so I'm sorry if my comment just added speed to wheels that are already spinning. Just hoped to provide a pointed statement of how one could conceptualize it. |
@0x24a537r9 I read #65 again, but still can not get why |
@hax As alluded to in the Usage section of the Github, it was intended to be a drop-in replacement for the existing pattern of One advantage of this scheme is that if you ever receive |
@0x24a537r9 Still no concrete use case 🧐 ... But I will try my best to understand your motivation according to your last comment. Please forgive me and correct me if I misread your comment.
Interesting idea, it seems you only want to deal with But the main advantage of const x = a?.b.c
if (x === undefined) {
// something maybe wrong, but what can we do here?
} And if you don't check the The only possible "correct" solution I can imagine for this direction is: Just throw Error instead of return The main problem of such semantic is: JS is not TS/Flow ... Especially for the guys who don't like static typings, they will very likely "abuse"
As my analysis before,
True. I believe the key point is if such tradeoff is practical. |
This change matches my intuition, and I seem to not be the only one. I support it. A lot of comments here and in other threads relate to what other libraries do. I believe there are several of these. Would anyone be interested in preparing a survey of the semantics of these libraries? It could be an interesting data point for comparison. |
The expectation of the “correct” result for (A) That interpretation is problematic in the face of short-circuiting (an objectively desirable feature) in expressions like (B) (After having written that comment, I realise that someone has already said that.) But besides mental model, there are certainly some situations where it would be more useful (and sound) to have |
As the majority seems to think that Consider the following function. At one point in time, black paint was the evident choice for cars: function purchasePaintForCar() {
// go to store and buy black paint
} Later, more dyes became available, so the function was refactored: function purchasePaintForCar(color = 'black') {
// go to store and buy a paint of that colour
} Now consider an object modelling a car, with a field let color = myCar.color // "red"
purchasePaintForCar(color) // buy red paint (As an aside, that will work even for legacy car objects that doesn’t have the Now, there is a provision for using let color = myFutureCar.color // null
purchasePaintForCar(color) // do nothing (don't buy paint) Or even it is not certain that I will have a car in the future: let myFutureCar = null
let color = myFutureCar?.color // null
purchasePaintForCar(color) // don't buy paint, even not black paint |
@claudepache Now imagine that the car data is coming from a server, and we call one of the following: purchasePaintForCar(response.data.defaultCar?.cosmeticOptions?.color);
// OR
purchasePaintForCar(response.data.cars[0]?.cosmeticOptions?.color); In the empty case, Under your approach, these exhibit different behavior. Whether this is "correct" or not isn't too important (e.g., we could imagine a |
Why would defaultCar be |
@ljharb Because it's a server you don't control, I guess. 😛 |
I don't think either is clearly better than the other. always
maybe
|
Given two choices where one makes the other impossible, and the other makes both possible, we should probably prefer the latter? |
Personally, I lean that way. But, |
Regarding the second, what if I want to know if a value exists, but is
null? Then we're needlessly complicating what should be a simple query.
…On Fri, Jul 27, 2018, 14:59 Justin Ridgewell ***@***.***> wrote:
I don't think either is clearly better than the other.
always undefined:
- Pros
- Lodash does this
- Cons
- It'll surprise some people
- It can't be easily normalized to null
maybe null or undefined:
- Pros
- It can be easily normalized with ??: maybeNull?.prop ?? undefined
- Cons
- It'll surprise some people
- Lodash doesn't do this
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#69 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ADA9M2dzx7ASDQQ45k1VrI78Xw15Wb01ks5uK2MEgaJpZM4VdHsk>
.
|
Is there a another language which has
? |
@jridgewell regarding normalisation: In the first scenario, why can't it be normalized to maybeNull?.prop ?? null And regarding the 2nd scenario: maybeNull?.prop ?? undefined What if I'd like to distinguish the case where I think with such normalization, there is always a loss of information, in both cases. I don't see one being better necessarily than the other, just good for different kinds of situations. |
I don't understand. You don't need to normalize.
Because you can't tell if it was really However, only cared if it was
Then don't normalize? |
|
// this is ugly
const firstName = message?.body?.user?.firstName || 'default';
// maybe like this
const firstName = message.body.user.firstName ?? 'default'; |
@sndwow if you think it’s ugly, don’t write code like that (but i don’t agree). Those two things have very different semantics, and the second doesn’t allow you to, say, make |
Here is a concrete use case where let xn = x?.normalize("NFC") When |
@claudepache can you elaborate more on why if x is null, you’d need xn to be null instead of undefined? |
I think the essential issue is what information we can get from the result, especially how differentiating
If use TS/flow, compiler only allow
Basically, type system eliminate most Now let's introduce optional chaining operator, use As current semantic,
We can see current semantic just combines the first two cases and keep last unchanged. If use TS/flow, it becomes
As
We can see the first case are splitted and mixed into other two cases. If use TS/flow, it becomes
Summary Current semantic make
I think TS/flow users would definitely prefer current semantic, considering Even only consider JS, in long chaining like As
As current semantic,
Could |
And, I think current semantic is more consistent with semantic of nullish coalescing operator. Actually I would like this proposal rename to "non-nullish chaining" or "nullish stop chaining" 😉 |
Isn't default the nail in the coffin for I mean who wants to have to write |
I don’t need it to be specifically either null or undefined. But given that undefined and null have sometimes different semantics (as a fact of life, not judging whether it is appropriate), it may be dangerous to change null into undefined or vice versa in an otherwise unrelated operation (here, unicode normalisation). Compare: foo(x);
// carelessly refactored as:
foo(x?.normalize("NFC")); (Even if we agree that it would be generally weird for foo() to consider |
Thanks for explaining, ftr it makes the most sense to me that optional chaining shouldn’t change the LHS. |
@hax We can discuss at length what semantics is more appropriate in theory, and I don’t think we can find an answer that is always correct, even if there are chances that one of the semantics is more often correct. (But do note that I gave an example of
I am less worried about what information I get from |
Unfortunately though, this phrasing already presupposes the " * I actually fear that the phrase "always |
I guess i see |
I think I tend to view the specific desugaring as secondary, i.e., as a representation of the proposed semantics which must then be verified, as opposed to an expression of the fundamental motivation. In particular, there's no expectation that the most convenient way to write something with Thus we could have a before-and-after scenario like this: // before
function getFooStatus(options) {
return options && options.fooData ? options.fooData.status : defaultStatus;
}
// after
function getFooStatus(options) {
return options?.fooData?.status ?? defaultStatus;
} These clearly aren't equivalent under any desugaring, but each might be called "the easy way" given the features available. So while the desugarings you've mentioned represent the proposed semantics of each camp, I'm encouraging that we recognize that these representations merely follow from each camp's axioms—are we just querying for |
On the other hand, If It's also nice if |
foo(x);
// carelessly refactored as:
foo(x?.normalize("NFC")); This is an interesting example. If I understand this example correctly, we are assuming But similar cases also occur in other side: const {x} = bar || {};
foo(x);
// refactored to:
foo(bar?.x); Assume |
If taking into account the JSON representation of javascript objects, I find 1) and 2) I think of Also, If
That said, I would be okay with any approach, just want that this feature (optional chaining) be included in javascript. |
I agree |
well, it depends on how you check. const obj = { a: null, b: undefined };
Boolean(obj.a); // false
Boolean(obj.b); // false
Boolean(obj.c); // false
// property actually exists, but null or undefined
'a' in obj; // true
'b' in obj; // true
// property does not exist
'c' in obj; // false
// ignores undefined's
JSON.stringify(obj); // {"a":null}
// the properties really are in the object
Object.keys(obj); // ["a", "b"] just providing more context/info to how js works. not expressing an opinion. |
@obedm503 I don't think that was the point. var a = {};
console.log(a.b); // undefined |
@obedm503 I'm well aware that a key with an 1) A property defined with 2) Like I said in that post: 3) It's common to see a variable defined as 4) This issue is mainly between What you said is valid and it's important to keep in mind, but I don't think it conflicts with what I said in that post and the reasons to choose |
It's quite surprising that But since |
Why on earth would you write |
I would not write this intentionally for sure, but it's valid syntax, and static property accessor is a left-to-right operator, so I could expect a same result But I'm fine with the current status, it's just maybe something to be aware of |
Now that the proposal is at stage 4, that won’t change. |
In other words, the desugaring of
a?.b
should bea == null ? a : a.b
(instead ofa == null ? undefined : a.b
as currently specced).I gave the following justification for having
a?.b === undefined
whena === null
in README#FAQ (emphasis added on the contended part):However, that justification is flawed, because of the short-circuiting semantics: when
a
is null, the name of the eventual property (here,"b"
) is not sought, so that there is no property to give information about.This is clearer when the property name is computed, as in, e.g.:
In case of
a?.[foo(a)]
, ifa
is null, we do not compute the value offoo(a)
(and we couldn’t, iffoo(null)
throws a TypeError), so that it doesn’t make sense to try to ”give information about the propertyfoo(a)
ofa
”.I think it is more sensible to apply the usual semantics of short-circuiting operators (
&&
and||
), at least for consistency reason, namely: In case of short-circuiting, evaluate to the last encountered value.Incidentally, that would avoid to clear the null/undefined distinction of the base object, see #65.
The text was updated successfully, but these errors were encountered: