Regarding `Foo()` being identical to `Foo(undefined)`

In Why is BigInt() broken? I noticed this comment by @ljharb:

We’ve tried pretty hard to avoid that legacy pattern, and make absence and undefined always be treated the same.

I can't help but wonder if this doesn't introduce a potential issue.

If Foo() is identical to Foo(undefined), then that seems to imply that Foo(undefined, undefined) is also the same as Foo(), as would any length of undefined parameters. Even if this seeming implication is false, the original statement seems to contradict the behavior of arguments in general.

function Foo(...args) {
   console.log(`argument count: ${args.length}`);
}

In the end, such a convention seems to be just as awkward as property presence detection done as such:

let a  = { bar: undefined; }
if (a.bar === undefined) {
   ...
}

Please do not argue on the poor nature of such a test. Any such argument is moot as, sadly, code with similar construction exists in the wild.

In both cases, the detectable presence of a value is being ignored. I'm not arguing that the legacy pattern should be continued. I would simply like to have an understanding of the desire to change the well-known, and occasionally useful pattern.

Code exists in the wild for everything that's possible, but "code exists" isn't a justification to encourage it as a pattern. Many things are detectable that are often ignored in builtins - methods coerce to number/string instead of throwing on non-numbers/non-strings, as of ES6 most things that expect an Object no longer throw on non-nullish primitives, etc. The strong idiom in the language itself is to be liberal in what is accepted - in other words, to intentionally ignore detectable things when it makes sense, is intuitive and/or wouldn't cause incorrect or surprising behavior.

Note that the above is an argument for the general case, that it is both acceptable and good for the language to make design choices.

Separately, a function that checks arguments.length, or that behaves differently between a missing parameter and an explicitly undefined one is passed, is highly likely to be a very confusing one - and I'd guess wouldn't be considered very idiomatic.

Similarly, writing a function that checks that an object lacks a property versus that it's set to undefined is highly unfriendly to any inheritance patterns - subclassing in particular.

While I personally would like BigInt() to return 0n and BigInt(undefined) to throw, in the general case I think it is a very bad idea to differentiate between "absent" and "undefined", and my comment on that thread was pointing out the unfortunate situation we find ourselves in, where neither option is free of downsides.

2 Likes