Why is BigInt broken?

Perhaps you're right in that I do not understand the point of a default constructor. In my own classes, I rarely ever allow the user to initialize it without explicitly defining what they want it to be, because it removes ambiguity and unpredictability. I never use constructors like String or BigInt without any arguments, because I have no reason to, and find writing out the value more readable.

As far as "neutral" values concerns, I find that concept a bit subjective (why is the neutral Number not 1, since it's equal to its own inverse? Why is the property "being equal to its own negative" perceived as it being neutral?). Perhaps the neutral value for BigInt should be Infinity, as it's the biggest int I can think of, though it's not an object of type BigInt, so it throws an error. I know Infinity is technically not an integer, or mathematically speaking, it's not even a real number, but my point is that these "neutral" values could be somewhat subjective. I understand what you're saying though; while I would never use String() or BigInt() without arguments, I can see your point on why people would. I don't necessarily agree personally with that pattern, but that's not what we're here for to discuss.

As far as the language defending developers, I think it is essential that we move towards this. Of course, developers should learn to use different aspects of the language, but there's no reason not to steer them a little in taking the right path without having to think about it. This doesn't mean the language should be any more or less powerful - things like not allowing to call String() without any arguments doesn't make the language less powerful, because one could achieve the same result with String('') (I'm not saying we should move to that specifically; this is demonstrating that you can have the same language capabilities with fewer syntactical options).

You can see a similar pattern in everyday objects. If you have trouble using the washing machine, because it has 40 buttons on it and you don't know which ones to push in which order, then as the designer of that washing machine I could say "bah, people should just learn to use it!" but that's not what design is about. It should be intuitive and easy to use; ideally, you shouldn't even have to think about what buttons to push.

The same goes for programming languages. We can give it all the bells and whistles and expose these in as many ways as we like, or we can wrap up the same exact bells and whistles in a package that's easy to use and steers people towards using it flawlessly without them having to think about it too much.

I hope that clarifies my previous comment a bit.

1 Like

@vrugtehagel Thanks for the clarification. I would agree with you on the issue of simply not calling a constructor without the required argument if the concept of a default value didn't exist. However, regardless of what that value is, as long as it exists, default constructors are there to initialize objects to that value.

As for the language defending developers, maybe you'll see my point if you look at the differences in purpose between a programming language and a washing machine. While indeed, a washing machine is a device with many options controlled by complex parameters, its intent is to do what appears to be a simple job for people who don't want to put much though into it because it appears simple. A programming language, by contrast, has the daunting purpose of providing access to the exposed capabilities of a computer so that a user of the language can translate human requests into a series of instructions the computer can understand and execute. In other words, we as developers perform the same kind of job that the control board in the washing machine does, translating end user desires into instructions that some hardware can execute. It's our responsibility as programmers to take the complexity of controlling the hardware away from the end users. We cannot perform our job well if the language we're using treats us like end users.

That's the last I'll say about that so as not to polute the thread further.

1 Like

I am not sure if or how exactly this is related, but if it is, the breakage it introduces with Wasm exports that are called with omitted i64 values (via JS-BigInt-integration), leading to

Uncaught TypeError: Cannot convert undefined to a BigInt

due to being unable to create a default value, seems like it is important enough to address. Like, there are certainly good reasons to gradually improve the language, but as soon as stuff breaks, even the best arguments around here become moot imo.

That is, unless there is a(nother) way to solve this at the Wasm<->JS boundary?

Consider that String and BigInt return primitives, not objects. They really act more like coercion operators than constructors. And between that and the fact func() is equivalent to func(undefined) provided func doesn't care about argument count, String() and BigInt() are equivalent to String(undefined) and BigInt(undefined) respectively.

@claudiameadows Not quite right. For some function:

function func(...args) {
   return args.length();
}

func() != func(undefined). They are not "equivalent". They can be treated similarly only if the presence of an argument is unimportant. That's what's at the root of the discussion. The somewhat disturbing trend to let SomeBuiltIn() behave the same as SomeBuiltIn(undefined), while useful in many cases, isn't useful in every case. Due to that inconsistency, it is IMO better to err on the side of consistent control and let an undefined parameter cause error where appropriate. It isn't clear how useful it is to have BigInt(undefined) return the same as BigInt() when even Number(undefined) returns NaN.

You missed a bit:

(emphasis added)

I intentionally didn't make any specific reference to how a function would detect it (whether via arguments.length or args.length given an ...args parameter), BTW.

You got me. I did miss that fragment. Yet my reply still stands. I might change the example a bit, though.

function func(...args) {
   return JSON.stringify(args);
}

Now the function doesn't care about the argument count, at least not directly, and that's also part of the point. There are many places where the presence or absence of a parameter makes a difference. That's why I brought up Number. Even though the language clearly defines that calling a function that has arguments with no argument values sets each argument to undefined, Number() !== Number(undefined). ES has traditionally accepted that these 2 forms have different meanings. My point is that breaking this useful convention can have unexpected, hard to debug consequences.

Still cares about argument count - JSON.stringify accesses args.length, just indirectly. Even stuff like this would still mean it cares about the number of arguments:

// Example 1: `foo()` returns `"fake value"`
Array.prototype[0] = "fake value"
function foo(...args) { return args[0] }

// Example 2: `bar()` returns `"fake value"`
Object.prototype[0] = "fake value"
function bar() { return arguments[0] }

Of course, nobody in their right mind would do this in the wild. But it's for the sake of example.

But my point still stands - they care about arguments.length, just not directly. However, positional parameters are still initialized as undefined, and can be directly desugared in strict mode to var name; if (arguments.length >= N) name = arguments[N] at least within the older function style.

I probably should've been more precise - I mean functions that do not use arguments or rest parameters for any reason other than to resolve positional parameters don't see a difference between the two.

1 Like

Good job, I hate different behaviours on absence and explicit undefined parameter, because developers don't always want to go into the detailed implementations and check whether the method has treated in different ways, especially when it is natively implemented.

1 Like

But String() is not equivalent to String(undefined).

Just tested, and you're right. :woman_facepalming:

hahaha, call Symbol() I get 'symbol()'. It's the one that ruins the whole aesthetic

Except it's not.

> Symbol(undefined).toString()
'Symbol()'
> Symbol("").toString()
'Symbol()'
> Symbol().toString()
'Symbol()'

It's still consistent.

sorry, I didn't make it clear. It refers to BigInt