Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Number.isFinite dont have type guard?

Can someone ask me why Number.isFinite dont have type guard like number is number?

THis example provides an error Object is possibly 'undefined'

function inc (n?: number) {
  return Number.isFinite(n) ? n + 1 : 1;
}

Playground:

https://www.typescriptlang.org/play?ssl=1&ssc=1&pln=3&pc=2#code/GYVwdgxgLglg9mABDSiAUYD8AuRYQC2ARgKYBOAlIgN4BQiiZJUIZSAcoaWQHQwDOAMRQwoJDFUx5EAakQBGRLnkBuWgF8gA

like image 417
dima.krutoo Avatar asked Mar 19 '21 07:03

dima.krutoo


People also ask

Can I use number isFinite?

isFinite is a function property of the global object. You can use this function to determine whether a number is a finite number. The isFinite function examines the number in its argument. If the argument is NaN , positive infinity, or negative infinity, this method returns false ; otherwise, it returns true .

What is number isFinite?

isFinite() The Number. isFinite() method determines whether the passed value is a finite number — that is, it checks that the type of a given value is Number , and the number is neither positive Infinity , negative Infinity , nor NaN .

How do you know if a number is finite?

isFinite() returns true if a value is a finite number. Number. isFinite() returns true if a number is a finite number.


1 Answers

First some background. On first glance, this seems to be related to the difference in JavaScript between

isFinite

which will first convert its argument to number and

Number.isFinite

which happily accepts all arguments, regardless of type, and simply returns true only for finite numbers (with no implicit conversions). In TypeScript these have signatures

function isFinite(number: number): boolean

(method) NumberConstructor.isFinite(number: unknown): boolean

In the second method, the parameter type is unknown instead of number. The reason is that in JavaScript, Number.isFinite happy returns false for non-numbers (whereas isFinite coerces first).

> Number.isFinite(false)
false
> isFinite(false)
true
> Number.isFinite(Symbol(2))
false
> isFinite(Symbol(2))
Uncaught TypeError: Cannot convert a Symbol value to a number at isFinite (<anonymous>)

Now since Number.isFinite is already expected to accept all arguments from any type and return false no matter how wacky the arguments, it makes sense for this behavior to be preserved in TypeScript. Hence the parameter type properly is unknown. For the global isFinite which is intended to work only on numbers (and does the coercion that TypeScript helps us avoid), it makes sense to restrict the parameter to being a number.

But as your question rightly asks, why, given that Number.isFinite will accept any arguments and return true only for numbers, why can't it be a type guard? The reason is that it returns true for some, but not all numbers! Let's try at least to write our own guard:

function numberIsFinite(n: any): n is number {
    return Number.isFinite(n)
}

and these make sense:

console.log(numberIsFinite(20)) // true
console.log(numberIsFinite(Number.MAX_VALUE)) // true
console.log(numberIsFinite(undefined)) // false
console.log(numberIsFinite("still ok")) // false

But here is where things go wrong-ish:

console.log(numberIsFinite(Infinity)) // false but uh-oh
console.log(numberIsFinite(NaN)) // false but uh-oh

Now it's true that with this type guard that

const x: number|string = 3
if (numberIsFinite(x)) {
  console.log(`A number whose type is ${typeof(x)}`)
} else {
  console.log(`A string whose type is ${typeof(x)}`)
}

will print

"A number whose type is number" 

so far so good but:

const x: number|string = Infinity
if (numberIsFinite(x)) {
  console.log(`A number whose type is ${typeof(x)}`)
} else {
  console.log(`A string whose type is ${typeof(x)}`)
}

will print

"A string whose type is number" 

And this is silly.

If there was a dependent type in TS called "finite number" then (and perhaps only then) would making Number.isFinite a type guard make sense.

Credit to @VLAZ, whose answer this explanation is based on. I added the second part to my original answer after reading theirs.

like image 133
Ray Toal Avatar answered Nov 14 '22 23:11

Ray Toal