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 numeric range types #319

Merged
merged 22 commits into from
Nov 30, 2021
Merged

Add numeric range types #319

merged 22 commits into from
Nov 30, 2021

Conversation

jonahsnider
Copy link
Contributor

This PR adds "numeric range" types, number/bigint types for validating "ranges" of numbers (ex. integers, positive numbers)

Inspired by the Nim programming language's Natural and Positive range types.

They can be useful for validating and documenting parameters.
For example, the BigInt function could be typed as

declare function BigInt<T extends number>(number: Integer<T>): bigint;

and prevent the following code from being a runtime error:

BigInt(1.5);
//=> throws RangeError

Other examples are included in the doc comments.

@sindresorhus
Copy link
Owner

sindresorhus commented Nov 15, 2021

You need to add them to the readme.

@sindresorhus
Copy link
Owner

Zero is technically not a positive number, so maybe we should rename Positive to NonNegative?

@sindresorhus
Copy link
Owner

Would you be willing to add a NonNegativeInteger too? I think it's quite a common need.

@sindresorhus
Copy link
Owner

These types are not just enforcing ranges though, but also types. Maybe it would be more correct to name the file numeric.d.ts?

@sindresorhus
Copy link
Owner

Note to self:

@sindresorhus
Copy link
Owner

@sindresorhus
Copy link
Owner

I'm also wondering whether we should be explicit in the types: Positive => PositiveNumber?

source/numeric-range.d.ts Outdated Show resolved Hide resolved
*/
// `${bigint}` is a type that matches a valid bigint literal without the `n` (ex. 1, 0b1, 0o1, 0x1)
// Because T is a number and not a string we can effectively use this to filter out any numbers containing decimal points
export type Integer<T extends number> = `${T}` extends Infinities ? never : `${T}` extends `${bigint}` ? T : never;
Copy link
Owner

Choose a reason for hiding this comment

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

I wonder how this technique compares to https://stackoverflow.com/a/69413070/64949

A `number` that is an integer.

Use-case: Validating and documenting parameters.

Copy link
Owner

Choose a reason for hiding this comment

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

Are there any open TS issues about adding an integer type that we could link to here?

Copy link
Contributor Author

@jonahsnider jonahsnider Nov 23, 2021

Choose a reason for hiding this comment

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

microsoft/TypeScript#28682 seems to be the best fit

@jonahsnider
Copy link
Contributor Author

Zero is technically not a positive number, so maybe we should rename Positive to NonNegative?

Positive<0> is never.

Would you be willing to add a NonNegativeInteger too? I think it's quite a common need.

I have added PositiveInteger<T> and NegativeInteger<T> types.

These types are not just enforcing ranges though, but also types. Maybe it would be more correct to name the file numeric.d.ts?

Renamed.

Should we add a FiniteNumber type too?

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isFinite

I have added a Finite<T> type which accepts a number (since bigints are always finite).

I'm also wondering whether we should be explicit in the types: Positive => PositiveNumber?

I would advise against this as those types can accept a number | bigint, putting Number in the name might be misleading.

@sindresorhus
Copy link
Owner

Positive<0> is never.

When do you ever need only a positive number though? I always want zero too. I also fear the type will be confusing to users as not everyone knows that a "positive" number does not include zero.

@sindresorhus
Copy link
Owner

Positive<0> is never.

Also, then the docs you have provided are confusing as you show the range (0, ∞), which is read as inclusive, meaning from zero to infinity.

@sindresorhus
Copy link
Owner

I have added PositiveInteger and NegativeInteger types.

I don't really see the usefulness of these. NonNegativeInteger is useful as it includes zero. I don't think it's common to need an integer that is from 1 and up, so I think that's better left to composition with the Integer and Positive type.

@sindresorhus
Copy link
Owner

Also, the exact definition of "natural number" seems to be unclear:

Some definitions, including the standard ISO 80000-2,[3][a] begin the natural numbers with 0, corresponding to the non-negative integers 0, 1, 2, 3, ..., whereas others start with 1, corresponding to the positive integers 1, 2, 3, ...[4][b] Texts that exclude zero from the natural numbers sometimes refer to the natural numbers together with zero as the whole numbers, while in other writings, that term is used instead for the integers (including negative integers).[5]

@sindresorhus
Copy link
Owner

I have added a Finite type which accepts a number

Needs to be added to the readme. There are some other missing types in the readme too.

@sindresorhus
Copy link
Owner

I suggest we rename Natural to NonNegative. It will then be coherent with NonNegativeInteger.

declare function setLength<T extends number>(length: Natural<T>): void;
```

@category Utilities
Copy link
Owner

Choose a reason for hiding this comment

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

Should we maybe make a new Numeric category?

@jonahsnider
Copy link
Contributor Author

When do you ever need only a positive number though? I always want zero too. I also fear the type will be confusing to users as not everyone knows that a "positive" number does not include zero.

I think there are some use cases for 1 or more vs 0 or more (mostly just enforcing a minimum value).

Also, then the docs you have provided are confusing as you show the range (0, ∞), which is read as inclusive, meaning from zero to infinity.

That's not correct, in range syntax a ( is exclusive while a [ is inclusive. Probably would be best to just rewrite this as an inequality since they're harder to misinterpret (ex. 0 < x < ∞).

I suggest we rename Natural to NonNegative. It will then be coherent with NonNegativeInteger.

I'm going to rename the types to have more useful names rather than follow strict mathematical definitions. It's clear that trying to apply those concepts to software ends up being unnecessarily confusing.

@sindresorhus
Copy link
Owner

I think there are some use cases for 1 or more vs 0 or more (mostly just enforcing a minimum value).

Sure, but it's not a common case.

That's not correct, in range syntax a ( is exclusive while a [ is inclusive.

I don't think there's any common standard for this. Personally, I'm mostly familiar with range syntax from Swift which is inclusive: 0...4 meaning 0 to 4, including 0 and 4. But yes, let's make it completely clear.

@jonahsnider
Copy link
Contributor Author

Sure, but it's not a common case.

Fair enough, I have removed the Positive and PositiveInteger types.

@sindresorhus sindresorhus merged commit 0e26d18 into sindresorhus:main Nov 30, 2021
@sindresorhus
Copy link
Owner

Very nice work. Thanks for adding these 👍

@jonahsnider jonahsnider deleted the feat/numeric-ranges branch November 30, 2021 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants