Closed d4nyll closed 6 years ago
Definitely do not agree with this idea.
This is magic and implicit. If I want some of the member accesses in the chain to be required, then the current proposal allows it - whereas yours would force me to separate my chain into multiples.
Yes, I agree that the implicit nature of this proposal bugs me too. Thanks for your feedback!
An explicit alternative might be an additional syntax sugar, something like:
// applies optional chaining to all subsequent property lookups
const name = ?user.settings.account.lastname
// an equivalent of
const name = user?.settings?.account?.lastname
I think this hints on a real world vs theory thing for me, I think the majority of use cases for optional chaining will be defensive programming, where 95% of the time developers are wanting to deeply access a property as in the example above.
Saying it's magic might be a hint that the implementation may not be possible but I think it's important with new syntax to be open minded.
The proposed chain assumes a common expectation about the entire access chain. It's entirely possible that we might want user.settings
to throw if user
is undefined.
While the current syntax might be slightly verbose for deep access chains, it's possible to express heterogeneous expectations about that chain.
I don't think the concept disagrees or disallows that scenario, it's more about favouring less verbosity for the common case.
FWIW, I'm playing devils advocate as I think decisions should be driven by data, not emotion - a feeling might suggest there's a problem, but should be backed up with some analysis.
For an argument that was well written up I think it deserves that.
I don't think the concept disagrees or disallows that scenario
While it technically doesn't prevent the expression of heterogeneous expectations, it becomes far more verbose:
if (user == null) throw('user is undefined')
const name = user.settings.account?.lastname
By making assumptions about the entire access chain, the proposal greatly reduces the operator's flexibility in order to save just 2 characters in the example. That's a poor tradeoff.
user.settings.account?.lastname
clearly says to me that i should get an exception if user
or user.settings
isn't an object, but that user.settings.account
doesn't have to exist - in which case name
would be null or undefined.
After reading through the feedback, I agree with @tvald's comment that having ?.
evaluate everything to the left of it would reduce the flexibility of the operator, and thus would not be good.
But @meandmycode makes a good point that many of the use cases would be for defensive programming, and thus having something that caters for the (arguably) main use case would be good.
I'd bet that once this proposal is accepted, this shorthand would be one of the first thing many people would be asking for.
So in that light, what do you think about @darsain's idea of having an operator that does allow for that:
// applies optional chaining to all subsequent property lookups
const name = ?user.settings.account.lastname
// an equivalent of
const name = user?.settings?.account?.lastname
what do you think about @darsain's idea of having an operator that does allow for that
It would be great but that would require to support the parentheses version as well:
// applies optional chaining to all subsequent property lookups
const name = ?user.settings.account.lastname
// check first 3 only (being explicit about the part of the chain that we are unsure about)
const city = ?(user.settings.account).address.city
Optional chaining and nullary coalescing are already syntactic sugar. I would propose no sugar be added to the sugar. Too much sugar.
@levithomason imho, the whole point of these things is to make code more concise and comfortable to write, and this:
?user.settings.account.lastname
Is the 99% use case we should be focusing on. Just imagine how annoying it is going to be typing the question mark before each dot:
user?.settings?.account?.lastname
I just prefer explicit and a little more verbosity.
I don't think it is a good thing to make things more concise for the sake of being concise. I subscribe to the code is for humans not machines idea, and I believe it applies to syntax as well. It is the same reason I've come to reject almost all forms of abbreviation in code. We have minifiers for making things concise, outside of that let's make it human readable and explicit.
Just my 0.02.
Annoying, or clear and explicit?
Someone will write a babel-plugin for it anyway ;)
... 95% ... ... 99% ...
Completely wrong.
According to statistics gathered from CoffeeScript usage in https://github.com/tc39/proposal-optional-chaining/issues/17:
... Total soak operations: 4627 ... Total soak operations chained on top of another soak: 564
564/4627 is about 12%, not >90%.
@claudepache is right.
My initial thoughts was that the repositories used by coffeescript-soak-stats are mostly libraries and simple applications. Maybe usage by more complex applications (with more complicated data structures) would be different.
But this is not the case. When I ran the same tests with the most popular CoffeeScript applications on GitHub (sorted by stars), as opposed to libraries and applications, the percentage that used "soak operations chained on top of another soak" was around 11%.
2426/2426
Total files: 2425
Total lines: 418841
Total soak operations: 3879
Total soaked member accesses: 3224
Total soaked dynamic member accesses: 185
Total soaked function applications: 467
Total soaked new invocations: 0
Total soak operations using short-circuiting: 1157
Total soak operations using short-circuiting (excluding methods): 170
Total soaked assignments (including compound assignments): 34
Total soaked deletes: 1
Total cases where parens affected the soak container: 0
Total soak operations chained on top of another soak: 434
The following repositories were used:
sharelatex
- 1/8 or 12.5%codecombat
- 287/2547 or 11.3%yakyak
- 82/246 or 33%taiga-front
- 3/58 or 5.2%talk-os
- 46/888 or 5.2%cloudtunes
- 2/32 or 6.3%cyclotron
- 13/100 or 13%Interesting to note that in the yakyak repository, usage was at 33%, whereas others are as low as 5.2%. It implies that for applications with heavily nested data structures, they will use consecutive optional chaining frequently, and would benefit heavily with the ?user.settings.account.lastname
syntax. Other applications which do not have heavily-nested structures would not benefit from this, however.
I still think ?user.settings.account.lastname
is a good idea. You can be explicit and verbose with the user?.settings.account?.lastname
syntax if you want to define how to group the checks. But by offering an additional sugar with ?user.settings.account.lastname
, you can help those developers who primarily uses it for deep access of object properties to keep their code more concise and readable.
11 - 12% isn't a large percentage, but it's still significant enough.
Great to see some real data 👍 the stats seem to disagree that this would be the common case, but I'm not sure they accurately say that developers would rather be explicit.
The stat only counts chained use, but it's not considered against if the developer even has the opportunity (i.e., it would be better to see counts for deep property access a.b.c.d
vs chained a?.b?.c?.d
, or for x deep access that are n deep, y used soak).
I'm not sure they accurately say that developers would rather be explicit.
But, as language designer, I want to force them to be explicit (unless strong reason against that), for their own good and for the sake of those that will read their code...
The current Optional Chaining proposal is carefully designed, so that the developer has the opportunity to express exactly what they mean, not less and (importantly) not more. That is, when I write:
a.b?.c.d(x).e
I express that that a.b
may be null for some valid reason. But I don’t expect that a
is null, and, in case a.b
is not null, I don’t expect that either of a.b.c
, a.b.c.d
or a.b.c.d(x)
is null; and if my expectation is wrong, I want a TypeError to be thrown in my face rather than swept under the rug, so that I am aware that there is some logic error earlier in my code.
As shown by the stats, cases where there are at least two property accesses in the chain and you want optional chaining at each level, is not the general case, not even the majority case.
Optional chaining and nullary coalescing are already syntactic sugar. I would propose no sugar be added to the sugar. Too much sugar.
Agreed. Healthy diet also includes salt.
Whilst I'm personally not sure this additional syntax is needed (an implied syntax version), I'm not sure it's as simple as this is bad language design because its not explicit, look at statically typed languages for example that have evolved to imply types in various places, I think a more accurate argument might be that it feels unusual for current ecmascript and it's value add over the explicit syntax is too minor.
But consider operators like typeof
, instanceof
or even regex options, which change the context of what the next/prior expression mean, there's already the ability to change execution aspects of sibling expressions today.
@meandmycode typeof
only avoids a ReferenceError on its RHS, otherwise, it and instanceof
are just binary operators. There's not anything prior to this proposal that I can think of that changes how member expressions are evaluated.
Yes certainly not the context I was suggesting.
I'm following this thread for some time now, and I'm not convinced yet that this is a good idea. And the lack of consensus seems to confirm that
What's wrong with one of the listed "workarounds"?
(user.settings.account)?.lastname
Seems explicit enough to me.
Sure, the workaround is explicit, but it's the proposed default behavior that's being criticized.
This line isn't explicit:
user.settings.account?.lastname
Well then I propose that the parentheses variant be the "default behavior". To be clear, that would be it, nothing else.
For those who don't like reading
I think the consensus here is that the original proposal to have user.settings.account?.lastname
be equivalent to user?.settings?.account?.lastname
is a bad idea, purely on the grounds that it does not allow the user to be explicit. As stated in the My Thoughts section in my proposal, I wasn't sure it was a good idea, except when combined with the Nullary Coalescing operator. Having read everyone's comments, it's clear that my original concern was valid.
What me and @meandmycode are still arguing for is the additional syntax of having ?a.b.c.d.e
be equivalent to a?.b?.c?.d?.e
on the grounds that it is much easier to read the former than the latter.
The counter-argument (or part of it) is that statistically, there's not enough demand for that use to justify the additional sugar. My counter argument is that for some projects, the usage is still high enough that this additional syntax would be valuable.
Ultimately I wanted to ensure we were open minded about syntax, to avoid a so-called baby duck syndrome - I think it's important to challenge your own gut-feeling (which I think it has* been).
For what it adds I personally think the additional syntax is conceptually OK but statistically questionable if people would use it and it does feel like something that could only be reasoned about properly after optioning chaining in its current form is the norm in the ecosystem.
TL;DR In short, I just don't think it is a good idea 😕 Just type a few question marks when needed.
What me and @meandmycode are still arguing for is the additional syntax of having ?a.b.c.d.e be equivalent to a?.b?.c?.d?.e on the grounds that it is much easier to read the former than the latter.
Over the years I've found most readability issues, both mine and others', to be resolved with only a few weeks exposure. After that, we learn to read differently and it feels normal. In my experience, resistance or unwillingness to change is often labeled a readability issue for convenience.
That said, I don't believe there is anything more readable about either one of these syntaxes given some time and syntax highlighting.
The counter-argument (or part of it) is that statistically, there's not enough demand for that use to justify the additional sugar. My counter argument is that for some projects, the usage is still high enough that this additional syntax would be valuable.
For me, this is hardly the main case. I do not think that because something is valuable that it also must be a good thing. I also don't believe that popularity is necessarily an indicator of a good thing either.
I have many exceptions to the proposal that I haven't shared. Here are some of my main gripes:
I see no precedence for shorthand repeat operators, and duplicating the pattern feels wrong:
const foo = first || second || third
// vs
const foo = || first second third
const total = 1 + 2 + 3 + 4
// vs (polish notation)
const foo = + 1 2 3 4
I see no mirrored syntax to complement or justify the shorthand, such as in object destructuring and object shorthand:
const foo = { one: 1 }
const { one } = foo
const one = 1
const foo = { one }
@ all: Thanks for your input. I’ve added an entry in the FAQ explaining briefly why we won’t do that. If you think that this FAQ entry ought to be improved, do not hesitate to share your suggestion.
I have an idea that incorporates parts of #9, that I wish to discuss here.
Proposed Amendments
Let's say we have this:
This will turn into the following with the Optional Chaining Operator:
This is a very common use case, and I'd argue the most common use case for the optional chaining operator. Would I be jumping the ship in saying it'll be cleaner to simply have this:
So that everything left of the
?.
would be evaluated 'step-by-step' and if any of the steps arenull
orundefined
, then the expression returnsundefined
The semantic of the operator would change from:
To:
In the of the current proposal, you have the example:
So what if
b[3]
evaluates toundefined
? Then it would throw an error. By having the?.
operator evaluates the entire chain, we can omit having many?.
operators.Why this might not be a good idea
user.settings
object always exists, then this new proposal would be performing 2 superfluous checks?
after the penultimate operand, which can seem unnaturalWorkarounds
user.settings
exists, then we can groupuser.settings.account
together so the?.
operator will treat that group as one block. I.e.(user.settings.account)?.lastname
New syntax - maybe something like
user.settings.account.lastname ?| 'default'
, where?|
will essentially be the same asuser?.settings?.account?.lastname || 'default'
. The'default'
may be omitted to produce the same result as the current proposal -user.settings.account.lastname?|
would be the same asuser?.settings?.account?.lastname || undefined
, which is the same asuser?.settings?.account?.lastname
This will actually allow the operator to be much more powerful, as it can incorporate the Nullary Coalescing operator into the optional chaining operator.
My Thoughts
I feel like the current proposal is good enough, and allows you to be explicit rather than implicit, with the small downside of the code looking more verbose than it could be. I feel this change is unnecessary, as it is, but if it can be combined with the Nullary Coalescing operator, it could be a worthwhile discussion.
Tagging @tvald as he commented in a relevant prior discussion Tagging @gisenberg as he is the author for the Nullary Coalescing operator proposal Tagging @claudepache, @ljharb, @Mouvedia, @nerfpops, @adaptabi, @rattrayalex, @ittledan for their participation on #9.