Open calebmer opened 5 years ago
Duplicate of #195.
This is a much more limited proposal. I only care about bitwise operators. I want to use the type system to force developers to coerce to an int32
with n | 0
in performance critical code.
I wouldn’t advise most developers use this feature.
To that end, I’d instead recommend naming the type BitwiseInt32
to discourage usage of this type as a general integer type like #195 wants.
In JavaScript, you can safely represent integers up to 253-1. However, for bitwise operators you only have a range of integers -231 through 231-1. If there were to be an int
type you might want it to cover the full JavaScript safe integer range. However, for bitwise operations specifically you want a tighter range.
I guess the problem here is that people would generally expect *
, -
, and +
to follow int32 + int32 = int32
, even though that is definitely not something that could be guaranteed.
It seems like you really just want a brand here?
It’s actually desirable that the type for BitwiseInt32 + BitwiseInt32 = number
because that forces the user to then coerce with | 0
. So if you want to write a type safe addition you’d need:
function addInt32(x: BitwiseInt32, y: BitwiseInt32): BitwiseInt32 {
return x + y | 0;
}
Which has the right properties if you’re looking to do int32 math in JavaScript.
That’s why I recommend calling the type BitwiseInt32
instead of int32
. You don’t want the user to expect int32 + int32 = int32
. If you make the name more ugly, then hopefully user’s won’t attach their assumptions to what the type should do.
What about a uint32
type for >>> 0
?
Would #33290 help in defining/implementing this? I'd like to see a handful of these types defined. At the very least, int
, uint32
, and int32
. Unlike #195, I would argue that these should definitely not affect the emit (a non-starter). This would enable a number of things:
int + int = int
but not int32 + int32 = int32
. The latter should fall back on the former.| 0
or >>> 0
and others.int
(and maybe other similar types for other functions), Number.isInteger
could be made into a type guard, and then the false branch of if (Number.isInteger(stringOrNumber))
would correctly still include number
(see #21199).I would like #195. I want to use int32
for performance reasons: I have one use case where numbers up to 2**31-1
is enough (array indexes, I know these can go up to Number.MAX_SAFE_INTEGER
but I do not care about handling numbers that large), and where coercing parameters and the output of every arithmetic operation by adding | 0
(for instance, ++i
~ (i = i+1|0)
) yields significant time saving and produces compiled code almost twice smaller even though more subroutine calls are inlined (10KB instead of 17KB, I used Indicium to witness that).
I think what I have experienced can be extrapolated. I have the feeling that this addition would make basic algorithms in JavaScript much less resource consuming (if you agree to limit yourself to inputs of reasonable size). This claim would need to be evaluated.
I agree this example use case is a niche. I do not want int32
to be the new number
. Even more so if it means errors due to implicit integer division or integer overflow.
Indeed a workaround is to have a branded int32
type, a coercion function, and arithmetic functions. But I have sufficiently many arithmetic operations in my code that I fancy leaving the responsibility to TypeScript would not be a luxury. I am probably wrong. Maybe all we need is a tiny library which includes said type and functions.
Nervetheless, if this responsibility would be shifted to TypeScript, I can see two problems in code emission:
PS: If going the workaround route then explicit inlining hints would be relevant (see https://github.com/microsoft/TypeScript/issues/661 for instance). The current v8 is excellent at inlining what needs to be inlined, I do not deny it, I have seen it in action and it is impressive. That does not contradict wanting more control on code emission. The better argument would be that implementing this takes time both for conception and maintenance, and has limited applicability. If we want to argue code size, replacing a(x,y)
by x+y|0
uses one less character but a(a(x,y),z)
would need parentheses (x+y|0)+z|0
and now we are equal, inlining a(a(a(x,y),z),w)
emits something with one more character. Not sure if output size is relevant. The case (i = i+1|0)
would be better served by a macro or a code transform as you cannot wrap the assignment in a function call but if we cannot have that then (i = f(i))
already saves one character because the right operand is constant. Hence we cannot argue inlining will always save bytes, nor can we argue it will always use more. A reasonable argument against implementing inlining hints in TypeScript is that minifiers can do that job, giving even more control (although, at the moment, terser produces an IIFE for some (each?) inlined call, and there is no way to guide the process).
I'd like to point out the fact that JS "canonically" has these types: Int32Array
and Uint32Array
. Sometimes, I must use these arrays as "wrappers" for those primitive types, because they offer "implicit coercion on overflow". But having to do arithmetic on a constant index of 0
is too awkward:
// BEGIN BOILERPLATE
interface FixedInt32Array<T extends number> extends Int32Array {
length: T;
}
interface Int32ArrayConstructor {
new<T extends number>(length: T): FixedInt32Array<T>;
}
interface FixedUint32Array<T extends number> extends Uint32Array {
length: T;
}
interface Uint32ArrayConstructor {
new<T extends number>(length: T): FixedUint32Array<T>;
}
type Int32 = FixedInt32Array<1>;
type Uint32 = FixedUint32Array<1>;
// END BOILERPLATE
const
// verbose initialize to 0
n: Uint32 = new Uint32Array(1),
// can't do `Uint32Array([3])`, because of type signature
m: Uint32 = new Uint32Array(1);
// values still mutable, despite `const`
m[0] = 3
const addU32 = (a: Uint32, b: Uint32): Uint32 => {
// can't reuse `a`, because of side-effects
const ret: Uint32 = new Uint32Array(1);
// `a[0] + b[0]` can overflow, so we use `ret` for safety
ret[0] = a[0] + b[0];
return ret;
}
// annoying `[0]`, everywhere
console.log(addU32(n, m)[0]);
( some code borrowed from https://github.com/microsoft/TypeScript/issues/18471#issuecomment-329588688 )
By that point, devs would just import
a package such as this. Or use something like
Number(BigInt.asUintN(32, BigInt(x + y)))
Search Terms
bitwise operators, integer type, int32
Suggestion
Add an
int32
subtype fornumber
returned by TypeScript from bitwise operators.JavaScript bitwise operators coerce arguments to int32 and the return type of those operators will always be an int32.
This would not be a breaking change since
int32
would be a subtype ofnumber
. Only bitwise operations would change to returnint32
s.EDIT: I now recommend calling the type
BitwiseInt32
so that user’s won’t see the type as a generic integer type.Use Cases
Helps applications which are trying to take advantage of JavaScript implementations
int32
optimizations. An application could ask for anint32
parameter to force coercion withn | 0
.Checklist
My suggestion meets these guidelines: