Different default values for different undefined levels
xareelee opened this issue · 2 comments
Please see the discussion in facebook/idx#5 for the reason about why we need different default values for different undefined levels.
In this example:
const result = props.user.friends[0].friends
We have 5 chances to get undefined
:
props == undefined
props.user == undefined
props.user.friends == undefined
props.user.friends[0] == undefined
props.user.friends[0].friends == undefined
We need some symbol to define the default values for different undefined
levels.
?(...)
will provide a default value if the evaluation returnsundefined
?>
will try to look up a default value in the optional chain?(...)!
will provide a default value only for this evaluation (it will not be looked up for?>
)?
will not try use a default value
For example:
const defaultValue = []
const result = props?(defaultValue).user?>.friends?>[0]?>.friends?>
^^^^^^^^^^^^^^Provide the default values and it can be looked up
We provide a defaultValue
to props
if it is undefined
. If any following evaluation returns undefined
and it use ?>
, it will look up the default value in the chain.
props == null
? defaultValue // use `?(defaultValue)`
: props.user == null
? defaultValue // use `?>` to look up the default value
: props.user.friends == null
? defaultValue // use `?>` to look up the default value
: props.user.friends[0] == null
? defaultValue // use `?>` to look up the default value
: props.user.friends[0].friends
? defaultValue // use `?>` to look up the default value
: props.user.friends[0].friends // final result
If we use ?(...)!
, for example:
const result = props?(defaultValue)!.user?>.friends?>[0]?>.friends?>
^^^^^^^^^^^^^^^Provide the default values and it can NOT be looked up
the defaultValue
can't be used in other optional levels (only for its level):
props == null
? defaultValue // only for this level
: props.user == null
? undefined
: props.user.friends == null
? undefined
: props.user.friends[0] == null
? undefined
: props.user.friends[0].friends
? undefined
: props.user.friends[0].friends
We could provide different default values for the chain:
const defaultFriends = [{ name: "Pavlos"}]
const result = props?(defaultValue).user?>.friends?>[0]?(defaultFriends).friends?>
props == null
? defaultValue
: props.user == null
? defaultValue
: props.user.friends == null
? defaultValue
: props.user.friends[0] == null
? defaultFriends // provide a new default value for this level
: props.user.friends[0].friends
? defaultFriends // look up for the default value
: props.user.friends[0].friends
We could use just ?
to not use a default value
// Only using `?>` will try to look up the default value
const result = props?(defaultValue).user?.friends?[0]?.friends?>
props == null
? defaultValue
: props.user == null
? undefined // will not look up
: props.user.friends == null
? undefined // will not look up
: props.user.friends[0] == null
? undefined // will not look up
: props.user.friends[0].friends
? defaultValue
: props.user.friends[0].friends
How about this proposal?
That seems quite complex for me. The goal of this proposal is to have simple, easy to understand semantics. The only real complexity is the short-circuiting behaviour.
But let see how to handle your use cases, using the current ?.
operator plus the null-coalescing ??
operator (which I think should be included, see #10):
const defaultValue = [] const result = props?(defaultValue).user?>.friends?>[0]?>.friends?> ^^^^^^^^^^^^^^Provide the default values and it can be looked up
const result = props?.user?.friends?.[0]?.friends ?? defaultValue
const result = props?(defaultValue)!.user?>.friends?>[0]?>.friends?> ^^^^^^^^^^^^^^^Provide the default values and it can NOT be looked up
For this one, the simplest replacement I can think of is:
let tmp = props
const result = tmp == null ? defaultValue : tmp?.user?.friends?.[0]?.friends
I think that we should stick if possible with the “left-to-right evaluation order plus short-circuiting” principle if possible. For example, let say that !
means “stop the evaluation of this expression” (short-circuit); one could write:
const result = (props ?? defaultValue!).user?.friends?.[0]?.friends
// Only using `?>` will try to look up the default value const result = props?(defaultValue).user?.friends?[0]?.friends?>
This one is more cumbersome; I wonder however whether that situation happens often.
const result = (() => {
let tmp = props
if (tmp == null)
return defaultValue
let tmp = tmp?.user?.friends?.[0]
if (tmp == null)
return undefined
return tmp?.friends
if (tmp == null)
return defaultValue
return tmp
})()
With the !
introduced above, it could be written as:
const result = ((props ?? defaultValue!).user?.friends?.[0] ?? undefined!)?.friends ?? defaultValue
But it is not clear to me whether your use cases occurs sufficiently often in real life to balances the burden to learn yet another syntax. Do other languages have solutions for your use cases?
Thanks to your suggestion. Because I want to restrict the scope of my work, I won’t include it. More specifically:
-
There are core issues on which I want to concentrate. For instance, the committee in charge of ECMAScript standardisation needs to be convinced whether short-circuiting is good semantics at all.
-
I share the sentiment with several people that the bar for any new syntax must be high, as expressed by someone else in: https://esdiscuss.org/topic/the-tragedy-of-the-common-lisp-or-why-large-languages-explode-was-revive-let-blocks. Your suggestion introduces some nontrivial complexity that needs to be strongly balanced with convincing use cases.
Of course, further improvements may be considered later if there are real needs, although I don’t expect to personally pursue the work in that direction. If you have any suggestion, you should make your case on the es-discuss mailing list.