Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add math intrinsic types #48198

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Add math intrinsic types #48198

wants to merge 1 commit into from

Conversation

skeate
Copy link

@skeate skeate commented Mar 10, 2022

Fixes #26382

With intrinsic types, it seemed like some simple type-level math would be a straightforward addition, so I wanted to try it out.

This adds several new intrinsic types to perform numerical operations on number literal types:

  • Integer<N> calls Math.floor on N
  • Add<M, N> evaluates to M+N
  • Multiply<M, N> evaluates to M*N
  • Subtract<M, N> evaluates to M-N
  • Divide<M, N> evaluates to M/N (floating point division)
  • LessThan<M, N> is M if M < N, otherwise never (removed, see comment below)
  • GreaterThan<M, N> is M if M > N, otherwise never (removed, see comment below)

The goal is to permit things like type-safe sized containers, beyond simple arrays/tuple-types, or anything else for which you might want to do some simple type-level math without having to resort to various hacky solutions (not that these are bad solutions, but they usually are hard to understand, are full of limitations, and make the typechecker do more work than should be necessary for this kind of thing).

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Mar 10, 2022
@typescript-bot
Copy link
Collaborator

The TypeScript team hasn't accepted the linked issue #26382. If you can get it accepted, this PR will have a better chance of being reviewed.

@ghost
Copy link

ghost commented Mar 10, 2022

CLA assistant check
All CLA requirements met.

@Cryrivers
Copy link

may i understand the specific use case you wanna address? imho this might not be a case that TypeScript is designed to solve.

@Jack-Works
Copy link
Contributor

LessThan<M, N> is M if M < N, otherwise never
GreaterThan<M, N> is M if M > N, otherwise never

Shouldn't LessThan return a boolean literal type instead of number/never?

@skeate
Copy link
Author

skeate commented Mar 19, 2022

LessThan<M, N> is M if M < N, otherwise never
GreaterThan<M, N> is M if M > N, otherwise never

Shouldn't LessThan return a boolean literal type instead of number/never?

Two reasons it's a number:

  1. For use as an argument type eg. const at: <K, N>(index: LessThan<K, N>) => <A>(collection: SizedCollection<N, A>) => A
  2. All the Calculation types return numbers this way, so e.g. this is true: eaa6142#diff-e9fd483341eea176a38fbd370590e1dc65ce2d9bf70bfd317c5407f04dba9560R5182

That said, those two are the ones I am least sure about. I think something like what's suggested in #43505 would be far better.

@roobscoob
Copy link

What about Subtract and Divide?

@roobscoob roobscoob mentioned this pull request Mar 22, 2022
4 tasks
@unional
Copy link
Contributor

unional commented Mar 22, 2022

@skeate skeate force-pushed the main branch 2 times, most recently from 499364f to 6ebbc75 Compare May 4, 2022 23:26
@skeate
Copy link
Author

skeate commented May 4, 2022

Updated this.

  • Removed the two comparisons.
    1. They were a bit confusing; they fit a use case I had in mind but might not be general enough.
    2. As noted in another comment, true inequality types like in Proposal: Interval Types / Inequality Types #43505 would be better, and I would worry adding this kind of half-measure would add resistance to that better solution.
    3. Most importantly, they can be implemented using the others added in here. See below.
  • I added Subtract and Divide. I originally didn't have them because, I thought, type Subtract<M, N> = Add<M, Multiply<N, -1>> but you can't do the same thing for Divide. Divide<x, 0> evaluates to never

Implementing the previous LessThan and GreaterThan with the others:

type LessThan<M, N> = `${Subtract<M, N>}` extends `-${number}` ? M : never
type GreaterThan<M, N> = `${Subtract<N, M>}` extends `-${number}` ? M : never

@Gottox
Copy link

Gottox commented Nov 6, 2022

This could be useful for example for the quickjs-API. quickjs borrows error handling from the C API, and therefore values < 0 represent an error, whereas 0 (or >= 0 on some functions) represents success. With this PR it is possible to enforce error handling through the type system.

return n;
}

function applyNumberMapping2(symbol: Symbol, a: NumberLiteralType, b: NumberLiteralType): Type {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is quite some code duplication, what do you think about for instance:

const MathArity2: Record<string, (a: number, b: number) => number> = {
    Add  : (a, b) => a +  b,
    Sub  : (a, b) => a -  b,
    Mul  : (a, b) => a *  b,
    Div  : (a, b) => a /  b,
    Pow  : (a, b) => a ** b,
    Hypot: (a, b) => Math.hypot(a, b),
    Min  : (a, b) => Math.min  (a, b),
    Max  : (a, b) => Math.max  (a, b),
};

And MathArity1 could have exp (E ** x), sin, cos, tan, abs, round, sqrt and your Integer (or just floor to keep it in sync with JS?).

(MathArity0 would be simply constants like PI and E, probably just a constants in the d.ts?)

@kungfooman
Copy link

@skeate Do you have any plans to merge it with latest changes? Otherwise this PR is amazing and I'm so happy I found it!

@kungfooman
Copy link

kungfooman commented May 30, 2024

I reintegrated this into the latest TS version (HEAD) and it still works like a charm, great work!

Playing around with this is interesting and I came across this:

type Add10<T extends number> = Add<T, 10>;
type Add20<T extends number> = Add<10, Add<10, T>>;
type tmpA = Add10<20>; // Outputs: 30
type tmpB = Add20<20>; // Outputs: 40

If you hover over Add20:

image

Should something like Add<10, Add<10, T>> be simplified into Add<20, T>? I assume this would explode the scope, but interesting anyway to contemplate the pros and cons. How do other languages handle this case?

@skeate
Copy link
Author

skeate commented May 30, 2024

I reintegrated this into the latest TS version (HEAD) and it still works like a charm, great work!

😮 I'm surprised; the conflicts looked pretty severe so I thought it'd basically require reimplementing the whole PR. If you want, I think you could PR that to my fork and then it should show up here. Though I'm not very hopeful that this will actually get merged; it was more a proof of concept/learning exercise.

I don't think that e.g. simplifying Add<10, Add<10, T>> would be in scope for this, as I suspect it'd require a lot of special handling of these new intrinsics.

I'm not sure if the more floating-point-oriented types have much utility. Because of the inherent imprecision of floating point math, it doesn't seem like it belongs in the type system. I was hesitant to even include Divide, thinking maybe integer division makes more sense. I went with it because you could get integer division with that and Integer<T>, but in retrospect I think integer division + modulus would be more useful.

If you have a use case for them, though, I'd be interested to hear it

@skeate skeate force-pushed the main branch 2 times, most recently from 526f96a to 8517071 Compare June 1, 2024 19:49
Co-authored-by: kungfooman <[email protected]>
@skeate
Copy link
Author

skeate commented Jun 1, 2024

@kungfooman thank you for your PR to bring it up to date. I hope you don't mind but I took the diff from microsoft/TypeScript:main and remade it as a single commit, just to avoid a gnarly history. I added you as a co-author in the commit message though.

@skeate skeate marked this pull request as ready for review June 1, 2024 20:03
@skeate
Copy link
Author

skeate commented Jun 1, 2024

.. Didn't mean to un-draft this PR. Oops.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Math with Number Literal Type
8 participants