Closed claudepache closed 5 years ago
Recall that this issue was opened specifically in order to resolve the inconsistency between a?.b
and a?.[b]
as raised in #5. From that perspective, &.
has no benefit over ?.
.
right. sorry about that, i should have read the previous issue more carefully
Another small problem of a&.b
is, the symbol &
looks too close to letters which lower the readability, though syntax highlight could solve this.
@hax !
already has an existing meaning for parens and brackets.
@codehag anything that only adds support for the dot, and not also brackets, is imo unacceptable. Symbols exist, and there must be a way to optionally look up Symbol-named properties.
@ljharb As I understand a![b]
or a!()
do not have conflict with current usage of !
(note there should have no newline between a
and !
). TypeScript (and Babel 7) already support it! see https://github.com/tc39/proposal-optional-chaining/issues/34#issuecomment-350641176 for more details about my analysis.
@claudepache: Recall that this issue was opened specifically in order to resolve the inconsistency between a?.b and a?.[b] as raised in #5. From that perspective, &. has no benefit over ?..
There’s also the alternative option of making x.[keyValue]
valid as a regular property access, along with the optional access x?.key
, and so forth. Was this alternative ever discussed? I just did a quick recheck and I haven’t seen it discussed here or es-discuss.
I don’t think adding a second syntax for computed property lookup would work out very well; that seems like a high cost.
Bringing the conversation from PR #48 back over to this thread, my favorite syntax proposition so far is also !.
, ![
, !(
, as proposed by @hax. I understand there is a conflict with existing TypeScript syntax. Does anyone have an idea of how much trouble it would actually entail to migrate the non-null assertion operator to a different syntax, like !!
?
Also, @ljharb mentioned that
!
already has an existing meaning for parens and brackets.
As in !(foo && bar)
? Seems distinct from baz.qux!(foo && bar)
. Is there another meaning you refer to?
Lastly, @Tiedye mentioned that !
usually implies inversion. While that is certainly true, the bang also often means required (as in TypeScript’s aforementioned non-null assertion operator or graphql schemas). With that meaning:
foo!.bar!()
Could read as:
foo
required (i.e. not null
or undefined
) for property access bar
bar
required for function invocation of bar
(An aside: @hax @lehni let’s make sure to continue the conversation in this issue thread, rather than in the PR thread, as requested)
@acusti I haven't thought through the grammar conflicts enough - it might be workable - but I primarily mean an existing conceptual meaning. !
in JS means negation, and in Ruby/Rails it means "mutates" or "throws". Using !
for "optional" doesn't seem to mesh with the meaning of "!" anywhere.
Using ! for "optional" doesn't seem to mesh with the meaning of "!" anywhere.
This is my biggest negative for using a bang. It means assertion in typescript and ruby. Why would someone reading it think optional?
If we use !.
/ ![
, we will be unable to put a new line before the !
(which is a serious issue for my coding style), because the following is valid today:
veryLongFooAndBar
![baz]
Any further thoughts on ?>
?:
x?>.y
x?>(y)
x?>[y]
?
?>
is close in syntax to the pipeline operator.
@TheNavigateur It looks like part of a scripting tag within an XML-based language, speaking as someone who wrote PHP in a previous career.
Based on @claudepache’s criteria, it:
Fulfills 1, 2, 3, 5, and 9 fully and without issue (is backward compatible; is reasonably easily parsable; works for static and dynamic property accesses, and function calls; uniform across the three syntactical cases; easy to type)
Partially fulfills 6, “partially suggestive”, thanks to the leading ?
Isn’t great for 4, “not used in mainstream languages for other purposes”, because it’s the syntax for the closing tag for PHP (as demonstrated by GitHub’s syntax highlighting given to it in @TheNavigateur’s comment)
Partially fulfills 7, “has precedent in other languages”, again thanks to leading ?
, though that argument is somewhat compromised by the contrasting precedent in PHP
Could fulfill 8 if we got used to it and internalized the notion that the >
suggests one part of the expression propagating in the direction of the arrow to the next part of the expression, though I personally don’t find it beautiful
Re: the pipeline operator, I find it visually fairly distinct, and only slightly more similar to the pipeline operator than to the fat arrow: |>
?>
=>
It seems to me that the strongest argument by far is the fact that it is clearly and easily distinguishable from ??
(the current proposed nullish coalescing operator). As such, I think it warrants serious consideration.
And based on ES5 syntax, I would’ve argued that the strongest argument against it as that it doesn’t look “javascript-y”. In that light, I would actually argue that the pipeline operator in some ways functions as an argument for the proposal by making it look less alien to the language.
@acusti it’s intentional and important imo that the nullish coalescing operator and optional chaining be similar, since they have similar semantics.
Isn't ?? close in syntax to the nullish coalescing operator? Doesn't this point hold more strongly since they exist in a similar problem space and yet have completely different meanings?
They both deal with a possibly nullish LHS, which determines whether the RHS is executed. Seems pretty similar to me.
@ljharb They exist in a similar problem space, but the semantics of optional chaining and nullish coalescing as an expression are very different: propagation vs switching . If a developer misreads or mistypes a character the similarity is more likely to lead to bugs.
Throwing my opinion in the ring:
Coming from no language other than JavaScript, I strongly urge that you please stick with the planned ?.?.[?.(
syntax laid out in the current README and implemented in the Babel v7 plugin.
From: claudepache's comment
I’m not going to review in detail all suggestions I have seen (they are numerous); but I’ll try to give some criteria for comparing their merits: the ideal but impossible syntax is the one that meets all criteria.
Here they are, approximately ordered by importance. Naturally, the real ordering will depend on your sensibility.
- backward compatible (i.e., something that does not have currently another meaning, or at least that is so obscure that it is practically unused) — because this is a strong requirement for web browsers for reasons;
- reasonably easily parsable (e.g., not something that requires infinite lookahead);
- works for static and dynamic property accesses, and function calls, i.e. x.y, x[y], and x(y) — because I want to support the three cases;
- not used in mainstream languages for other purpose, e.g.: ?? or ?:
- uniform across the three syntactical cases;
- suggestive — for instance the symbol ? may suggest “something with null” or “something conditional”;
- has precedent in other languages, typically: ?.
- beautiful;
- easy to type.
Now looking at few proposed syntaxes:
- ?. ?[ ?( — is almost perfect, but it doesn’t meet Criterion 2, which is very bad.
- ?. ?.[ ?.( — which is the current state, fails at least Criterion 5. People reported that ?.[ and ?.(, fail Criteria 6 and 8 for them.
- In general, Criterion 7 seems incompatible with the union of Criteria 1, 2, 3 and 5.
- ?&. ?&[ ?&( — is what is proposed in this Issue. It meets objectively Criteria 1 to 5, and it fits well Criteria 6 in my opinion.
- I’m leaving other proposals as exercise to the reader.
To address the criteria as they pertain to the as-planned ?.?.[?.(
syntax, here's my take:
?.?.[?.(
addresses all three cases (static & dynamic property access and method calls).?.?.[?.(
is not uniform across the three syntactical cases but I would argue it's a negligible intellectual leap to accept it. I imagine if I was new to JavaScript and came across any single use case, I'd see the ?.
syntax and google what it was. Then (in the future), I'd likely see an MDN article on the ?.
operator. From there on, it'd actually be easy to remember that the Optional Chaining Operator is always a ?
followed by a .
. It might actually be less mentally taxing that optional chaining is always signified by ?.
. If the syntax was ?. ?[ ?(
I imagine visually parsing it could sometimes get confusing as one's mind might conflate it with the ?
portion of a ternary operator, for instance. To take it to the extreme, the difference between ?.(
and ?(
could also cause confusion to the JS interpreter in some cases--especially in unsightly one-liner "golfed" code.Take, for example, this hideously golfed code that increments d
from 1
to 2
(among other things):
(a={b:{c:1}},d=e=f=1,a?a.b?a.b.c?(d+=f,++f):(d-=e,--e):0:0) // current JS - increments 'd'
d; // 2
Under the currently planned ?.?.[?.(
syntax, the following, shorter golfed code would still work as intended:
(a={b:{c:1}},d=e=f=1,a?.b?.c?(d+=f,++f):(d-=e,--e)) // '?.?.[?.(' syntax - still works
d; // 2
Under the proposed ?.?[?(
syntax, however, the a?.b?.c?(d+=f,++f)
portion of identical code would actually be misinterpreted as a method call and throw a TypeError because a.b.c is not a function, i.e., it would read it as (in current JS):
// '?.?[?(' syntax represented in current JS - breaks the code & fails to work as intended
(a={b:{c:1}},d=e=f=1,a?a.b?a.b.c?a.b.c(d+=f,++f):(d-=e,--e):0:0) // TypeError
d; // not evaluated because the above line throws 'TypeError: a.b.c is not a function'
edit: On second thought, the ?.?[?(
syntax probably wouldn't throw a TypeError because it would be implemented with a lookahead that would eventually see the subsequent :
portion of the ternary. However, it would certainly necessitate a much more complex lookahead (as @claudepache mentions in criterion 2) and make visually parsing the code much harder--especially if the :
was located much further ahead in the code.
[x] 6) For me, the ?.?.[?.(
syntax is suggestive of what it does:
The ?
suggests something checking for null/undefined/0/falsiness just like in a ternary, e.g.,
flag ? /* do something */ : /* don't do something */;
.
The .
suggests--again--to me, dot-notation access of course, but at a higher level, access of any kind just because dot-notation is the most common.
[x] 7) has precedent in other languages, typically: ?.
- ✓, apparently
[x] 8) The ?.?.[?.(
syntax is definitely more beautiful to me than any of the other options (some I've seen suggested elsewhere):
obj?.nestedObj?.nestedNestedObj?.prop
- easy to visually parse & beautiful
obj??.nestedObj??.nestedNestedObj??.prop
- harder to visually parse & ugly (imo)
obj?&.nestedObj?&.nestedNestedObj?&.prop
- much harder to visually parse & hideous (imo)
obj!.nestedObj!.nestedNestedObj!.prop
- easy to visually parse & OK aesthetically, but fails criterion 6 (imo) and may cause parsing issues when newlines are involved
obj?>.nestedObj?>.nestedNestedObj?>.prop
- only somewhat more difficult to visually parse, OK aesthetically, but also fails criterion 6 (imo)
obj&.nestedObj&.nestedNestedObj&.prop
- easy-ish to visually parse, but not as easy as ?.
or !.
, less OK-looking aesthetically and without giving it much thought, probably suffers from similar issues as !.
a side-note about the bang !
, however: from the current README:
The following is not supported, although it has some use cases; see Issue #18 for discussion:
- optional property assignment: a?.b = c
In the future, support might be added for !
to flip the logic; e.g., a!?.b = c
to assign a.b to c only if a.b isn't already defined. It's a bit of a stretch, but perhaps the !?.
would flip it from backward-looking (is a
defined?) to forward-looking (is a.b
defined?) and only set a.b
to c
if a.b
is undefined, i.e., !a.b ? a.b = c : undefined
.
There's likely a better behavior that !
could exhibit upon negating the ?.
operator, but if we were to instead use !.
, it would begin to lose sense seeing, for example, a!!.b = c
and leave less potential for some kind of !
support to be added later.
?.
is easier to type than ?&.
and some other suggestions; just try it out yourself--it's one less character and three keystrokes (shift
/
.
) vs. five keystrokes (shift
/
shift
7
.
), and the keys for ?
and .
are right next to each other vs. stretching to reach the key for &
.?.?.[?.(
syntax as is (for reasons).My take on this is still the same, keep ?.
and use parentheses for the rest by using one of the 2 solutions Iv already presented:
It's logical and only becomes bothersome (requires to know what you are doing) if you do something less common.
@ScottRudiger @Mouvedia I agree if uniform of three is not mandatory, the original ?.
proposal is the best.
Two leading char solutions (like ??
, ?&
, ?>
) are all suffer from ergonomics problem, more or less.
If we want both uniform and using just one leading char, the only possible solutions are:
a!.b
a![b]
a!(b)
a~.b
a~[b]
a~(b)
a@.b
a@[b]
a@(b)
a#.b
a#[b]
a#(b)
a..b
a.[b]
a.(b)
So, make a choice 😈
veryLongFooAndBar ![baz]
About linebreak problem, I believe mainstream coding style do not suggest
veryLongFooAndBar
[baz]
but
veryLongFooAndBar[
baz
]
So, you should write
veryLongFooAndBar![
baz
]
in same way.
And, in theory we can allow
veryLongFooAndBar!
[baz]
just as TS current usage of !
, though it's a little bit weird.
@Mouvedia I agree; the option you presented in distinguish seems reasonable.
My take on this is still the same, keep ?. and use parentheses for the rest by using one of the 2 solutions Iv already presented:
It's logical and only becomes bothersome (requires to know what you are doing) if you do something less common (e.g. using brackets).
However, I think just sticking with ?.
, ?.[
, and ?.(
is easier from a visual/usage/ease of remembering perspective (but not necessarily from a language-implementation one?).
Imagine this, somewhat, non-typical use case:
const arg = 'fez';
const a = {
b: {
"some method": (arg) => console.log(`I like to wear a(n) ${arg}`)
}
};
// w/ current JS - call "some method" with arg only if a && a.b && a.b["some method"] are defined
a != null ? a.b != null ? a.b["some method"] != null ? a.b["some method"](arg) : undefined : undefined : undefined;
// with ?. for dot & bracket access & method calls
a?.b?.["some method"]?.(arg);
// with optional (?) for dot access & required (?) for bracket access & method calls
a(?).b(?)["some method"](?)(arg);
Which option is preferable?
Personally, I think the version with ?.
looks relatively simple & succinct.
However, the version with (?)
begins to look slightly convoluted for my taste (there's something about too many closely packed parentheses that makes me perceive something as more complex than it necessarily is in reality).
Note: I (and most JS developers--I think) try to stick to the camelCase naming convention, but "method names with a space" have been known to exist in the wild.
Would there be a disadvantage to both allowing x?..y
for consistency, as well as allowing x?.y
as a permisiable shorthand for it?
The only disadvantage I can think of (at the moment) is confusion when reading the code, by someone who has been familiar only seeing one and doesn't know that the other is permissible.
Adding a ”consistent” syntax in addition to an ”inconsistent” one doesn’t resolve anything, because the goal of consistency is to have less syntax to learn.
Yeah I broadly agree with this.
@hax Can you clarify what you mean by "ergonomics problem", e.g. with ?>
. I am guessing you could mean "extra character to type". Yeah I think it's a little more effort to type, but once you've learned it, as claudepache suggested, the consistency means less to learn, both for writing and reading.
I'm curious if you mean something else though.
@ljharb I agree that having semantic familiarity with the nullish coalescing operator, while still making them visually distinct, is desirable. I was actually in the process of editing my comment right after posting it to clarify that my main issue with the ??.
-based proposal is two-fold (but then real life interfered):
foo??.bar
foo??:bar
are visually very similar??:
seems too long and arbitrary and entirely alien (not javascript-y)I feel like ??
as the nullish coalescing operator is a very desirable outcome. It matches ||
, but using a character that suggests a connection to optional chaining. ??:
loses all of those advantages and feels like too much of a compromise in comparison. I find myself running into the nullish coalescing use case regularly (defaulting values that could come through as null
), so I’m also very excited about that proposal and would really like it to have a reasonable syntax.
When I first read the proposal and saw foo?.bar?.[baz]?.()
, I thought it seemed entirely reasonable and had no issues with it. I intuited that I could just look for ?.
in an expression to identify optional chaining, and it felt consistent. I now understand how it is inconsistent, but I only got that upon reading about others’ issues with the syntax. Intuitively, the original proposal still feels consistent to me and always has. As such, I would second @ScottRudiger’s entreaty to keep the proposal as is.
If that is a non-starter, my second vote would be for ?>.
?>[
?>(
, which allows the nullish coalescing operator to remain ??
.
@TheNavigateur
About ergonomics problem, yes extra char is bad for input, but it also affect readability.
In mainstream coding style, we put spaces around operators. The exceptions are the highest precedence operators a.b
a[b]
a(b)
.
To correspond to them, aX.b
aX[b]
aX(b)
should also has no spaces around in most cases. (I use X
as the placeholder.) Without the spaces, if X
have similar shape and darkness to alphanums, it will affect the readability.
Here is some examples:
Two chars:
name3??.a_long_property??[computed_prop]
name3?&.a_long_property?&[computed_prop]
name3?>.a_long_property?>[computed_prop]
One char with high darkness:
name1@.a_long_property@[computed_prop]
name3#.a_long_property#[computed_prop]
One char with low darkness:
name1!.a_long_property![computed_prop]
name3~.a_long_property~[computed_prop]
For comparison:
name3.a_long_property[computed_prop]
name3?.a_long_property?.[computed_prop]
(current syntax)
We can see both ??
and ?&
are terrible, ?>
is better, but still can not compare to one char alternatives.
Black -> White (from http://paulbourke.net/dataformats/asciiart/)
$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,"^`'.
White -> Black (use Consolas font, from https://dboikliev.wordpress.com/2013/04/20/image-to-ascii-conversion/ )
`-.'_:,"=^;<+!*?/cL\zrs7TivJtC{3F)Il(xZfY5S2eajo14[nuyE]P6V9kXpKwGhqAUbOd8#HRDB0$mgMW&Q%N@
As these charts, !
and ~
are very low darkness char, which make them very recognizable
On the other hand, though @
have darkness higher than all alphanums, but its shape are more similar to alphanums than !~
.
And, ?>
is better than ??
?&
, because >
is a low darkness char and have a special shape. But the avg darkness of ?>.
is still higher than !.
~.
and more close to normal alphanum.
Last but not least, all current JS operators are only use 1 or 2 chars (even !==
just use 2 distinct chars), so I'm afraid ?>.
?&.
use 3 distinct chars will look very strange and disordered to many JS programmers, at least in the first glance.
@hax >>>
and >>>=
are both current JS non-word operators.
@ljharb >>>
and >>>=
are also only use one or two distinct chars.
As does ??:
, ??.
, ??[
, and ??(
:-)
As does ??:, ??., ??[, and ??( :-)
Yes, it does. My point of "3 distinct chars are unusual" is mainly reply to the "ergonomics problem of ?&
and ?>
".
Unfortunately, ??:
??.
have a very bigger ergonomics problem I already raised in the PRs. And @acusti comment https://github.com/tc39/proposal-optional-chaining/issues/34#issuecomment-368905503 also give a good summary.
Compare to ??. ??:
I would rather choose current syntax (?.
?.[]
?.()
??
) , or if uniform of three is desired anyway, why not a?..b
😸
I'd like to add another benefit of !.
![]
!()
proposal -- that is it avoid use of ?
(also apply to other alternative like ~.
~[]
~()
which do not use ?
char)
See https://github.com/tc39/proposal-partial-application/issues/21
Consider combination of partial application and null-aware operators:
const g = o?.f?.(?, 3) ?? h
-- current syntax, many ?
s
const g = o??.f??(?, 3) ??: h
-- PR syntax, oh my eyes...
const g = o!.f!(?, 3) ?? h
-- !
proposal, clear!
const g = o~.f~(?, 3) ?? h
-- ~
alternative, a little more clear!
Of coz, partial application proposal could (and have to) choose another char if we insist use ?
for optional chaining op. But only %
^
(maybe @
) are available for them. Personally I prefer leave ?
for partial application usage.
@hax Why would ?
become unavailable to the partial application proposal if this proposal uses it? This proposal won't make (?
or ,?
valid character combinations.
@ScottRudiger this line in your example
a?.b(?)["some method"](?)(arg);
should have been
a(?).b(?)["some method"](?)(arg);
Even if it's equivalent, for the sake of readability and consistency thorough, you ought to use the same token.
@Tiedye Yes it's valid, but the readability is so poor (see example in my last comment).
And even it's valid, it still limit the syntax (then semantic), @rbuckton said "the optional chaining proposal is one reason why I did not allow ?.x() as a means of marking the this receiver for a placeholder" -- from https://github.com/tc39/proposal-partial-application/issues/21#issuecomment-361092565
Note, ??.
syntax allow ?.x()
in theory, but you will never want to read/write
g = ^? ??. f ??( ?, 3 ) // I intentionally add some spaces around the ops, but still eyes harmful
@Mouvedia
a?.b(?)["some method"](?)(arg);
should have been
a(?).b(?)["some method"](?)(arg);
Even if it's equivalent, for the sake of readability and consistency thorough, you ought to use the same token.
Agreed, that makes sense stylistically.
I guess I was thinking from the perspective that dot-notation is probably the most common use-case--putting myself in the shoes of a developer who uses ?.
from time to time, but had to google optional chaining bracket-notation
or somesuch search term to remember the syntax.
I'll edit to include a(?).
though since it's the more sane style choice and since I happened to notice something else wrong with the comment too.
Wow, OK about ergonomics!
So then it's ergonomics vs learnability/intuitiveness.
?>
is learned once as a consistent thing you insert when you want optional chaining.
?.
is learned as a thing you insert when you want optional chaining, except before the .
operator you don't need the extra .
.
Personally, the 2nd thing bothers me more than the 1st, despite "ergonomic" superiority.
(a={b:{c:1}},d=e=f=1,a?a.b?a.b.c?(d+=f,++f):(d-=e,--e):0:0) // current JS - increments 'd' d; // 2
This earlier example shows you can already write fairly hideous JavaScript code (though the same can be said for almost any language). JavaScript is full of tokens with overloaded meaning, for example:
{}
for blocks, object literals, binding patterns, import/export names, and regular expression quantifiers.[]
for element access, array literals, binding patterns, computed property names, and regular expression character classes. ()
for grouping and invocation..
for property access, the fractional part of a floating-point number (not to mention 0..toString()
), and the .
character class in regular expressions.+
for binary addition, numeric coercion, and both pre- and postfix ++
-
for binary subtraction, negation, and both pre- and postfix --
,
as a separator in argument and element lists, as well as a binary expression.<
for less than, but <<
for left shift>
for greater than, but >>
and >>>
for right shift.In many of the above cases there are wildly different semantic meanings for the same token (i.e. blocks vs. object literals or arrays vs. computed property names). Overloading a token's meaning is nothing new, and isn't generally surprising. While I agree that an expression like o?.(?, b ? 1 : c ?? d)
starts to look unwieldy, it is an expression that can be decomposed to be more readable.
If token length consistency for the chaining sigil is paramount, my personal preference for a chaining sigil would be the family of ?.. ?.[ ?.(
, as it allows ??
to continue to be used for coalesce. That said, even if we did pursue either the ?..
or ??.
family of tokens, I still believe that ?
would remain a viable token for the partial application placeholder. I've even been considering leveraging prefix ?. ?[ ?(
as part of partial application since it would be syntactically unambiguous.
The reason I feel that ?
makes sense as an overloaded token for all of these scenarios is that they share a semantic meaning where ?
means "this has to do with some information I don't know":
a ? b : c
- "I don't know until the expression evaluates whether it will return b
or c
"a ?? b
- "I don't know whether a
is null or undefined, but if it is give me b
"a?..b
- "I don't know if a
is null or undefined, but if it isn't give me the result of a.b
"a?.[x]
- "I don't know if a
is null or undefined, but if it isn't give me the result of a[x]
"a?.()
- "I don't know if a
is null or undefined, but if it isn't give me the result of a()
"f(?, 1)
- "I don't know what the first argument should be yet, so give me a function that lets me fill it in later"?.x()
- "I don't know what object I need to call x
on yet, so give me a function that lets me fill it in later"a[?]()
- "I don't know the name of the method I need to call yet, so give me a function that lets me fill it in later"?()
- "I don't know the function I need to call yet, so give me a function that lets me fill it in later.Even in TypeScript we use ?
on some declarations with a similar semantic meaning:
class T { x?: number; }
- "I don't know if this object has an x
property, but if it does its type is number
or undefined
.function f(x?: string) { }
- "I don't know if the user will supply an argument for x
, but if they do it should be a string
or undefined
.All of this aligns with the general semantic meaning of ?
, in that it indicates a question to be answered. Something missing that needs to be provided. To me that means some form of ?..
or ??.
is the right fit for this proposal, as most of other tokens proposed don't align with that meaning.
@TheNavigateur
Not like ??
for nullish-coalescing, which is an exist syntax in broad used languages C#/Swift, all alternatives (except the original proposal ?.
) of optional chaining are new to all. So I'm afraid learnability/intuitiveness is very uncertain. In fact, I believe whatever syntax TC39 finally choose, most JS programmers can learn it in one or two day. But ergonomics is hard to change.
PS. all the alternatives could have good mnemonics:
a?&.b
-- a && a.ba?>.b
-- a -> a.b (if a is non-null forward to a.b)a!.b
-- a != null ? a.b (!=null then chaining)a~.b
-- ~ is just a perfect glyph of chaining So I don't worry about learnability/intuitiveness at all :P
PPS. I found that I start to love a~.b
a~[b]
a~(b)
, Does anyone have the same feeling?
@rbuckton I agree overloading meaning is usual and inevitable, but for one token, we'd better choose the meanings which rarely used together to ease the burden of our eyes.
Assume allow combination of null-aware ops and partial application:
o?.m?.(?, x?.[y] ?? z) // current syntax
o?..m?.(?, x?.[y] ?? z) // ?.. syntax
o??.m??(?, x??[y] ??: z) // PR syntax
o!.m!(?, x![y] ?? z) // ! syntax
o~.m~(?, x~[y] ?? z) // ~ syntax
With partial application support ?.
?()
?[]
g = ???.f??(?, x??[y] ??: z) // PR syntax
g = ? ??.f??(?, x??[y] ??: z) // same as below, add space to avoid triple ? disaster
g = %??.f??(%, x??[y] ??: z) // PR syntax, with % for partial application
g = ?!.f!(?, x![y] ?? z) // ! syntax
g = ?~.f~(?, x~[y] ?? z) // ~ syntax
You can imagine other weird cases such as g = ???[?]
🤣
Obviously, use different tokens for optional chaining and partial application are much more clear.
@hax
I'd like to add another benefit of
!.
![]
!()
proposal -- that is it avoid use of?
(also apply to other alternative like~.
~[]
~()
which do not use?
char)See tc39/proposal-partial-application#21
Consider combination of partial application and null-aware operators:
const g = o?.f?.(?, 3) ?? h
-- current syntax, many?
sconst g = o??.f??(?, 3) ??: h
-- PR syntax, oh my eyes...const g = o!.f!(?, 3) ?? h
--!
proposal, clear!const g = o~.f~(?, 3) ?? h
--~
alternative, a little more clear!Of coz, partial application proposal could (and have to) choose another char if we insist use
?
for optional chaining op. But only%
^
(maybe@
) are available for them. Personally I prefer leave?
for partial application usage.
If we absolutely must move away from ?
for optional chaining, the ~
is starting to grow on me.
I think I could live with something like:
const g = o~.f~(?, 3) ?? h
-- ~
optional chaining, ??
null coalescing, ?
partial application
But this just looks right to me:
const g = o?.f?.(@, 3) ?? h
-- ?.
optional chaining, ??
null coalescing, @
partial application
or if @
really is not possible for partial application:
const g = o?.f?.(%, 3) ?? h
-- ?.
optional chaining, ??
null coalescing, %
partial application
I'd still prefer using ?
over ~
for optional chaining because of the more clear meaning (in my mind) and closeness in association between optional chaining and null coalescing (not to mention the ergonomics of reaching for shift
+ ~
every time you need optional chaining. Personally, I think @
looks best for partial application, but could live with %
.
@ScottRudiger Regarding use of ~. Although I agree that the programming language is mainly designed for English keyboard I think a reasonable design goal to be as usable as possible on others. This becomes more important for common operators as they're typed so often - something I'd argue is the case for optional chaining. ~ is a terrible character on several European keyboard shortcuts as it belongs to the family of characters that are to be followed by another key in order to type it on Windows. For instance, on Swedish standard keyboards you type "Alt Gr + Å, then lift all keys and then space".
@staeke ~.
is not great on an English keyboard either, but that definitely sounds worse. By contrast, ?.
is very easy to type.
I don't familiar with European keyboards, but ~
(as Bitwise NOT) is already in JavaScript (and all C style languages) operators. So I will guess it's not a big problem for programmers at all.
@hax How often do you use the bitwise NOT operator?
Unless you are an adept of ~
preceding indexOf
? Or the floor double tilde?
@Mouvedia
I rarely use bitwise operators in JS, and I against~a.indexOf()
pattern. In fact as my daily eslint config, all bitwise operators are forbidden by default. But if I must write code for image processing, cryptology... I definitely need them.
My point is, though ~
as bitwise op only useful in specific domains, it exists in most programming languages for years. Also very common in unix command line (~
for user home path), and there are many other usages, eg. ~~through line~~
create through line in markdown... So it won't add much difficulty for programmers, they should already solve it or never mind it.
NOTE, I believe backtick ` also face the same problem (I guess in most keyboards backtick and tilde are coupled). But now it's heavily used for template string. So no reason to discriminate tilde from backtick.
This is going a bit deep into ergonomics, but despite backtick ` and tilde ~
being on the same key (standard US keyboard), I don't mind using backtick all the time for template literals or in markdown because you don't need to hold shift
to use the backtick. That might not seem like much, but it's very comfortable to just hit the ` key, but quite uncomfortable (for me) to keep my little finger on the left shift
key while pressing ` for the tilde ~
. You could tell me to use the right shift
button, but I don't think I'd ever get used to that.
@ScottRudiger To be fair, ?
also need press shift
, I use left shift
but I also know many use right 😉
Of coz ?
has a much better position compare to ~
in normal keyboard, but |
is also in the right-most place like ~
in the left. On my Macbook Air 13' keyboard, F
to ~
is about 98mm, J
to |
is about 111mm 😅
@hax it's not about just having to use the shift
key, it's about the way my left hand has to contort itself (lol) to reach left shift
+ ~
. But you made me realize, I don't always use left shift
. Literally the only time I use right shift
is for ?
and they're right next to each other.
After careful consideration (see in particular #5), I think that the best syntax satisfying the difficult constraints we face (strong BC, consistency) is the following:
Pros
?&
token distinct from.
is pedagogically better: One can picture the?&
token checking whether the LHS is null/undefined and performing short-circuiting if applicable, then the subsequent.
,[
or(
token performing regular property access or function invocation.Cons
?.
in C#, Swift, CoffeeScript and Groovy, and&.
in Ruby.)About the choice of ?&
?
recalls that we are dealing with null/undefined, as this character is used in several languages for denoting an optional/nullable value or object.&
recalls that the short-circuiting condition resemble the one of the&&
operator (replacing “falsy” with “nullish”).?
: “check for null”;&
: “perform eventual short-circuiting”.