Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to declare the Flow type for numbers that excludes infinities and NaN?

The built-in type number in Flow allows the "exotic" values such as Infinity, -Infinity and NaN.

How can I enforce the type to only allow real numbers?

EDIT. This is not a question how to check if a variable is real number. It is about typing with Flow.

I am looking for the way to write my functions like:

// @flow
function sum (x: real, y: real) { ... }

My question is how to define the type real so it works with Flow (http://flowtype.org/).

like image 548
Dmitri Zaitsev Avatar asked May 17 '16 08:05

Dmitri Zaitsev


2 Answers

You can't do that using Flow. You'll need runtime checks.

See discussion of the issues with real numbers here: https://github.com/facebook/flow/issues/1406

The bottom line is, pretty much any operation on real numbers could result in infinity, so distinguishing between real numbers and NaN / Infinity would not be very useful as it would return a type than is not guaranteed to be real.

For example,

Number.MAX_VALUE + Number.MAX_VALUE === Infinity
-Number.MAX_VALUE - Number.MAX_VALUE === -Infinity
Number.MAX_VALUE * 2 === Infinity
Number.MAX_VALUE / 0.5 === Infinity

Separately from that discussion, Flow doesn't have any facilities to blacklist certain values while allowing other values of the same type. You can only whitelist values, including using unions and intersections.

like image 92
Nikita Avatar answered Sep 21 '22 00:09

Nikita


There is a bit of middle ground you can strike with this with Flow. Yes, you'll need to use a runtime check to pull this off in the end, but you can construct an opaque type that will let Flow enforce you cannot bypass those validation functions. First, in one file, put this:

// @flow

// Define `Int` as an opaque type.  Internally, it's just a number.
// It's opaque because only this module can produce values of
// this kind, so in order to obtain an "Int", one _must_ use one of
// these functions, which (at runtime) will guarantee that these
// will always be integers.
export opaque type Int: number = number;

// Here's a function that will convert any number to an Int by running
// a typecheck at runtime and perhaps change the value (by rounding)
// This is the ONLY way of obtaining a value of the type Int   
export function int(n: number): Int {
    if (!Number.isFinite(n)) {
        throw new Error('Not a (finite) number');
    }

    // Round any real numbers to their nearest int
    return Math.round(n);
}

// In your private functions, you can now require Int inputs
export function isPrime(n: Int): boolean {
    // In here, you can assume the value of `n` is guaranteed to be an Integer number
    for (let i = 2; i < Math.sqrt(n); i++) {
        if (n % i === 0) return false;
    }
    return true;
}

Then, you use those like so:

// @flow

import { int, isPrime } from './lib';

isPrime(int(NaN));   // ok, but a runtime error, because NaN is not a number!
isPrime(int(3.14));  // ok, will effectively become isPrime(3)
isPrime(3.14);       // Flow error!
like image 26
nvie Avatar answered Sep 24 '22 00:09

nvie