tc39 / proposal-bigint-math

Draft specification for supporting BigInts in JavaScript’s Math methods.
https://tc39.es/proposal-bigint-math/
BSD 3-Clause "New" or "Revised" License
36 stars 2 forks source link

Thoughts on Math.sum/Math.product and implications for this proposal #23

Open bakkot opened 1 year ago

bakkot commented 1 year ago

I'm thinking about adding two new methods for doing sum/product. They'd take iterables of numbers: Math.sum([1, 2, 3]) === 6.

And of course if you pass Math.sum an empty array, you'd get 0 out. But then these methods can't really work with BigInts at all: you can't distinguish "an empty array of Numbers" from "an empty array of BigInts", so you can't tell whether 0 or 0n is the identity. And you can't reasonably have Math.sum([1n]) give 1n but also have Math.sum([]) give 0.

So I'm thinking there should be separate Math.sum / Math.bigSum methods, each of which only accepts values of one specific type.

If committee agrees with that analysis, maybe that informs the design of this proposal? Everything currently in this proposal can get away with polymorphism, but that's not going to be true for every possible method, so maybe it will make more sense to split methods into Number/BigInt versions even when we could theoretically combine them. (see also https://github.com/tc39/proposal-bigint-math/issues/14)

Alternatively, I suppose, we could have Number.sum and BigInt.sum. That would be pretty weird but it would at least avoid this tension.

ljharb commented 1 year ago

Number.sum/BigInt.sum is far less weird than Math.sum/Math.bigSum, but it's pretty unfortunate we can't just have a single Math.sum that works with both.

bakkot commented 1 year ago

Number.sum/BigInt.sum is far less weird than Math.sum/Math.bigSum

How so? There's several other Math operations which will remain Number-only (sin, for example), and the "bigX" naming convention is already established with Uint32Array/BigUint64Array.

And the overwhelming majority case is summing a list of Numbers; it would be a shame if users in the common case all had to remember that sum was in a different place than max and sqrt and so on just because sum can't take BigInts.

ljharb commented 1 year ago

"BigUint" is both close to the full "BigInt", and also being a Typed Array means it's already sufficiently weird so as not to be an automatic precedent or convention.

While I agree that would be a shame, I think sadly that's somewhat an unavoidable consequence of the committee's decision to minimize the ability to mix Numbers and BigInts - for example, Math.sum([1, 2n]) should work just fine, and it's a shame it can't, but previously asserted constraints (some of which are yours, iirc) would likely prevent that from being a reality.

bakkot commented 1 year ago

I think sadly that's somewhat an unavoidable consequence of the committee's decision to minimize the ability to mix Numbers and BigInts

Well, no, it's completely avoidable: Math.sum could work with numbers, and Math.bigSum could work with BigInts. And then the common case would be in the place users would expect it to be. Consequence avoided.

ljharb commented 1 year ago

right, but "bigSum" would imply "big sums" to me, not "sums of bigints", which is why i find that naming weird.

bakkot commented 1 year ago

I'd be fine with Math.bigintSum or Math.sumBigInts or something if that's the main concern. Or having Math.sum() and BigInt.sum(), for that matter. It's only really having Math.sum() which works on numbers which I'm attached to.

jakobkummerow commented 1 year ago

My vote is for BigInt.sum(); I've long thought that putting BigInt stuff onto BigInt.* is the cleanest way to organize things, and I see the point raised here as confirmation of this preference.

In my view, the mixed-type .max(0, 0n) example discussed elsewhere is somewhat related to .sum([1, 2n]). If we simply had non-mixed-type Math.max(0, 0) and BigInt.max(0n, 0n) then there'd be no problem, and we wouldn't need to debate which mental model among "prefer first", "prefer last", "prefer Number", "prefer same order for min and max", "prefer opposite order for min and max", "prefer however I happened to implement .reduce((a, b) => ...) last time I needed it" etc is most intuitive.

In fact, one could argue that the unresolvable ambiguity of .sum([]) applies just the same to .max(...[]): if you have BigInt-processing code that wants to do something with the maximum of a potentially-empty list, e.g. increment it, then 1n + Math.max(...list) will throw a TypeError if list === [].