rescript-compiler icon indicating copy to clipboard operation
rescript-compiler copied to clipboard

RFC: Introducing the Bigint

Open mununki opened this issue 1 year ago • 33 comments

Propose the Bigint in JS as a built-in type. mdn: https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/BigInt

Background

BigInt is a new primitive that provides a way to represent whole numbers larger than 2^53, which is the largest number Javascript can reliably represent with the Number primitive. It is not introduced yet to ReScript yet.

Proposal

Literal

nativeint is actually locked in ReScript. I'd like to suggest using the 'n' suffix for the bigint literal.

1n

Operators

Basically mostly same to the integer operators.

// arithmetic
1n + 1n
1n - 1n
1n * 1n
1n / 1n
2n ** 54n

// neg
-1n

// comparisons
2n > 1n
1n < 2n
1n >= 1n
1n <= 1n
1n == 1n
1n === 1n
1n != 1n
1n !== 1n

mununki avatar Dec 13 '23 09:12 mununki

Looking great! But I don't think the arithmetic operators can overlap with the regular int ones...? cc @cristianoc

zth avatar Dec 13 '23 09:12 zth

may related with #6477 and https://forum.rescript-lang.org/t/new-operator-for-bigint-support/3922

cometkim avatar Dec 13 '23 09:12 cometkim

Looking great! But I don't think the arithmetic operators can overlap with the regular int ones...? cc @cristianoc

Can't we just open the BigInt module for this?

tsnobip avatar Dec 13 '23 09:12 tsnobip

Can't we just open the BigInt module for this?

How do you mean?

zth avatar Dec 13 '23 10:12 zth

Looking great! But I don't think the arithmetic operators can overlap with the regular int ones...? cc @cristianoc

Yes, we can define new operator for the bigint, we can talk about in this thread. At the same time, I'm going to try to implement to make it using same operator with int, wonder how it looks.

mununki avatar Dec 13 '23 11:12 mununki

Can't we just open the BigInt module for this?

If I understand correctly, @tsnobip meant that Belt.Result.t<'a,b'> before it is moved to the predef.

mununki avatar Dec 13 '23 11:12 mununki

I just meant that the regular + is defined for big ints in RescriptCore.BigInt module for example, so when you open BigInt, you can use the regular + for big ints, and of course you won't be able to use it for regular ints anymore.

We can also define a special syntax for the big int operators, like +! for example.

tsnobip avatar Dec 13 '23 11:12 tsnobip

Similar to @cometkim suggestion, I'm wondering why the comparison operator only checks if the two arguments are the same type, and the arithmetic operator should be split between integer and float. Maybe it's for compiler efficiency, but why not just check if the two arguments are the same type like the comparison operator?

mununki avatar Dec 13 '23 13:12 mununki

I'm wondering why the comparison operator only checks if the two arguments are the same type, and the arithmetic operator should be split between integer and float.

Ocaml assigns types based on function usage. You need types assigned for certain optimizations. You couldn't have a compilation unit that exports a polymorphic function and expect it to work on BigInts, Floats, and Ints.

For example, compare the output of the two functions:

module Int = {
  external \"+": ('a, 'a) => 'a = "%addint"
  external \"-": ('a, 'a) => 'a = "%subint"
  external \"*": ('a, 'a) => 'a = "%mulint"
  external \"/": ('a, 'a) => 'a = "%divint"

  let add1 = (x) => x + (x / x)
}

module Float = {
  external \"+": ('a, 'a) => 'a = "%addfloat"
  external \"-": ('a, 'a) => 'a = "%subfloat"
  external \"*": ('a, 'a) => 'a = "%mulfloat"
  external \"/": ('a, 'a) => 'a = "%divfloat"

  let add1 = (x) => x + (x / x)
}

If you look at the source of polymorphic comparisons, it is much more complex. If you need performance, you should avoid using it.

CarlOlson avatar Dec 14 '23 06:12 CarlOlson

@CarlOlson Yes, indeed. The typechecking logics are quite complex than I expect 😄
Actually, I changed the arithmetic operations to polymorphic for int and bigint for test. I haven't benchmarked it yet, but I'm sure there will be a downgrade in compilation performance, and I'll have to modify the type checker to not accept any types other than int, float, and bigint.

let d = 1 + 1
let e = 1n + 1n
let f = 1.0 + 1.0
let g = "a" + "a" // should be type error
let h =
  {
    () => 1
  }() + 1

mununki avatar Dec 14 '23 07:12 mununki

I'm thinking it's a choice between adding another operator for bigint and changing the operators to a polymorphic type, what do you guys think?

mununki avatar Dec 14 '23 07:12 mununki

You could actually even keep it for strings, so it's like JS.

But I wouldn't trade any performance for this! If it has an impact, then adding operators for big ints would feel natural.

tsnobip avatar Dec 14 '23 07:12 tsnobip

You could actually even keep it for strings, so it's like JS.

I think the JS output maybe look okay, but looks weird in syntax layer. we have string concatenation ++ already.

mununki avatar Dec 14 '23 08:12 mununki

It's not that simple. It's what the rest of the world calls type classes (or extensions), and OCaml has been evaluating it for a decade (modular implicit).

What I proposed in #6477 is a much simpler solution and is how already done in F#

cometkim avatar Dec 14 '23 08:12 cometkim

I'm wondering why the comparison operator only checks if the two arguments are the same type, and the arithmetic operator should be split between integer and float.

This is absolutely necessary. The polymorphic + in JS actually differentiates int32 inside the engine. Thanks to ReScript's %addint, it is very friendly to JIT optimization. On the other hand, BigInt's + is not polymorphic. It will check the type early and throw a TypeError.

cometkim avatar Dec 14 '23 09:12 cometkim

Let's stick to a new set of custom operators then?

For the syntax itself, I have 0 inspiration, I'd say +! or +n (but this one feels a bit weird though consistent)

tsnobip avatar Dec 14 '23 10:12 tsnobip

For the syntax itself, I have 0 inspiration, I'd say +! or +n (but this one feels a bit weird though consistent)

Is there inspiration for the +! idea? I feel like +n is a bit more obvious for users coming from JS. It also would make future operators obvious, like +b if someone created a "bignum" library. Or +c for complex numbers, etc...

CarlOlson avatar Dec 14 '23 10:12 CarlOlson

Is there inspiration for the +! idea?

Nothing precise, just the fact that we need some character that is not already a math operator (+* or +- would obviously be weird), and in my head the bang ! can imply something big, like big ints :)

I feel like +n is a bit more obvious for users coming from JS. It also would make future operators obvious, like +b if someone created a "bignum" library. Or +c for complex numbers, etc...

The issue is that it'd be a first combination of a alphabet letter with a mathematical symbol and it would break the fact that symbols don't need to be surrounded with spaces:

let m = 2
let n = 1
let res = m+n 
// this is equivalent to 
// let res = m + n 
// what would happen now?

tsnobip avatar Dec 14 '23 11:12 tsnobip

Or, we can introduce only the obvious parts, such as type, literals, and untagged variants, and leave operators only in std modules as suggested in https://github.com/rescript-lang/rescript-compiler/issues/6477#issuecomment-1820645145. BigInt is usually used in isolated routines because it doesn't support polymorphic operations

cometkim avatar Dec 14 '23 11:12 cometkim

a backlink to an old discussion #4677

cometkim avatar Dec 14 '23 12:12 cometkim

I personally don't like addition of custom operators (+! or +n):

  1. It's already confusing to have +, +., ++ from the js user perspective
  2. BigInt is a rarely used feature. Mostly because it's not supported in older safari versions.

DZakh avatar Dec 14 '23 12:12 DZakh

BigInt is a rarely used feature. Mostly because it's not supported in older safari versions.

In my product, all DB IDs are treated as bigint (bigserial). protobuf-js also relies on bigint (fallback to long.js) and I actually stopped using ReScript when developing a similar codec because of missing bigint support.

bigint is much more common and has already achieved over 96% compatibility according to caniuse data.

Even the recent version of XCode has started to end support for iOS 14 and lower. (There is no emulator...)

cometkim avatar Dec 14 '23 12:12 cometkim

We do need the bigint for AOC 2024! (just kidding 😄)

mununki avatar Dec 14 '23 13:12 mununki

I think explicitly opening the BigInt module to get convenient operators isn't too much to ask. If you're working on a BigInt heavy codebase you can open it by default, and otherwise you can open it wherever you need to.

I'd rather do that than figure out which flavor-of-the-day operator I need to use for this type.

leostera avatar Dec 14 '23 13:12 leostera

But you can already use BigInt with rescript-core

DZakh avatar Dec 14 '23 14:12 DZakh

But you can already use BigInt with rescript-core

  • BigInt("100000") vs 100000n is different. n literal is more compact, statically checked, and optimized. (See https://github.com/rescript-lang/rescript-compiler/issues/4677#issuecomment-1002980370)
  • BigInt is a "primitive" in JS. Support for primitives is the basis for our interoperability claims.
  • It should be handled explicitly when we work with its closely related parts, such as untagged variants.

cometkim avatar Dec 14 '23 14:12 cometkim

IMHO, there are a lot of advantages to having it as a primitive type, and if we're stuck with the discussion of opening up the Bigint module instead of having it as a primitive type, it would be nice to have reasons why it's worse to have it as a primitive type.

mununki avatar Dec 14 '23 15:12 mununki

I have nothing against of having it as primitive type

DZakh avatar Dec 15 '23 07:12 DZakh

We'll need to have represented as a primitive in the compiler anyway for it to work with untagged variants etc.

zth avatar Dec 15 '23 09:12 zth

If we decide to go with primitive types, I think the debate is split between adding new operators and making the arithmetic operators polymorphic. Adding new operators is simpler than the latter. I'll try to prepare a PR for a poc to make the arithmetic operators polymorphic soon, and it would be nice to see the implementation and performance and continue the discussion.

mununki avatar Dec 17 '23 08:12 mununki