• typeof
  • instanceof
  • Number.isInteger()
  • Number.isFinite()
  • Array.isArray()
  • isNaN()
  • etc.

Okay, let's try to implement a function with type check.

// String check

let isString = function (str) {
   return typeof str === 'string' ? true : str instanceof String;
}


// Number check

let isNumber = function (n) {
    return !isNaN(parseFloat(n)) && isFinite(n) ? !isString(n) : false;
}

// or es6 feature

Number.isFinite(5);


let multiply = function (a, b) {
    if (isNumber(a) && isNumber(b)) {
        return a * b;
    } else {
        throw new TypeError('Arguments should be a numbers');
    }
}
multiply(5, 5) // 25
multiply(5.5, 5) // 27.5
multiply(5, '5') // Error
multiply(NaN, 5) // Error
multiply(Infinity, 5) // Error
// Array of numbers
Array.prototype.isArrayOfNumbers = function () {
	return this.every(cur => isNumber(cur));
}

let safeMathMax = function () {
	let args = [...arguments];

	if (args.isArrayOfNumbers()) {
		return Math.max(...args);
	} else {
		throw new TypeError('arguments should be a numbers');
	}
}

console.log(Math.max(1, 3, 'qwe'));    // NaN
console.log(Math.max(1, 3, '4'));      // 4

console.log(safeMathMax(1, 4, 5));	   // 5
console.log(safeMathMax(1, 4, '5'));   // TypeError
console.log(safeMathMax(1, [4], 5));   // TypeError

Safeness level

Working with types in different languages

Annoyance level

Flow type

  • Designed by facebook team
  • https://github.com/facebook/flow
  • https://flowtype.org

Flow can catch common bugs in JavaScript programs before they run, including:  

  • silent type conversions
  • null dereferences
  • and dreaded undefined is not a function
  • also you can use type notations, which can be easily transform in regular JavaScript

Let's see how Flow  works without type notations.

$ ./cli.js
../../script.js:3
  3: let multiply = (a , b) => a * b;
                                   ^ string. The operand of an arithmetic operation must be a number.


Found 1 error
/* @flow */

let multiply = (a , b) => a * b;

multiply(3, 'qwe');
// @flow

Math.max(1, 2, 3);
Math.max(1, 2, '4');
Math.max(1, 'a', 3);
flow-bin
$ ./cli.js
../../script.js:4
  4: Math.max(1, 2, '4');
     ^^^^^^^^^^^^^^^^^^^ call of method `max`
  4: Math.max(1, 2, '4');
                    ^^^ string. This type is incompatible with
167:     max(...values: Array<number>): number;
                              ^^^^^^ number. See lib: C:\Users\User\AppData\Loca                                                                                                                                                                                               l\Temp\flow\flowlib_2f923c5a\core.js:167

../../script.js:5
  5: Math.max(1, 'a', 3);
     ^^^^^^^^^^^^^^^^^^^ call of method `max`
  5: Math.max(1, 'a', 3);
                 ^^^ string. This type is incompatible with
167:     max(...values: Array<number>): number;
                              ^^^^^^ number. See lib: C:\Users\User\AppData\Loca                                                                                                                                                                                               l\Temp\flow\flowlib_2f923c5a\core.js:167

Found 2 errors

WOW

$ ./cli.js
../../script.js:3
  3: let getLength = val => val.length;
                                ^^^^^^ property `length`. Property not found in
  3: let getLength = val => val.length;
                            ^^^ Number

../../script.js:3
  3: let getLength = val => val.length;
                                ^^^^^^ property `length`. Property not found in
  3: let getLength = val => val.length;
                            ^^^ object literal

Found 2 errors
// @flow

let getLength = val => val.length;

getLength([1, 2, 3, 4]);             // 1
getLength('JavaScript');             // 2
getLength(5);                        // 3
getLength((a, b, c) => a * b * c);   // 4
getLength({length: 'five'});         // 5
getLength({flow: 'GOT YOU?'});       // 6
$ ./cli.js
../../script.js:8
  8:    sayHelloBad: () => console.log(`Hello! My name is ${this.name}`),
                                                                 ^^^^ property `name`. Property cannot be accessed on
  8:    sayHelloBad: () => console.log(`Hello! My name is ${this.name}`),
                                                            ^^^^ global object

Found 1 error
// @flow

let obj = {
	name: 'Nikita',
	sayHello: function () {
		console.log(`Hello! My name is ${this.name}`);
	},
	sayHelloBad: () => console.log(`Hello! My name is ${this.name}`),

	sayHelloBad2: function () {
		setTimeout(function () {
			console.log(`Hello! My name is ${this.name}`);
		}, 100);
	}
}

obj.sayHello();     // 1
obj.sayHelloBad();  // 2
obj.sayHelloBad2(); // 3
$ ./cli.js
No errors!
// @flow

let sumOrConcat = (a, b) => a + b;

sumOrConcat(1, 2);     // 1
sumOrConcat('qwe', 2); // 2
sumOrConcat(1, '2');   // 3
sumOrConcat('1', '2'); // 4

Ok, without type notations flow can handle some of common JavaScript "features" but not all.

Let's see how flow works with type notations.

// @flow

let b: number;

b = 5;
b = 'qwe';
b = null;
b = undefined;
b = {number: 4};
b = [1, 2 ,3];
flow-bin
$ ./cli.js
../../script.js:6
  6: b = 'qwe';
         ^^^^^ string. This type is incompatible with
  3: let b:number;
           ^^^^^^ number

../../script.js:7
  7: b = null;
         ^^^^ null. This type is incompatible with
  3: let b:number;
           ^^^^^^ number

../../script.js:8
  8: b = undefined;
         ^^^^^^^^^ undefined. This type is incompatible with
  3: let b:number;
           ^^^^^^ number

../../script.js:9
  9: b = {number: 4};
         ^^^^^^^^^^^ object literal. This type is incompatible with
  3: let b:number;
           ^^^^^^ number

../../script.js:10
 10: b = [1, 2 ,3];
         ^^^^^^^^^ array literal. This type is incompatible with
  3: let b:number;
           ^^^^^^ number


Found 5 errors
$ ./cli.js
../../script.js:8
  8: multiply('1', '2');
              ^^^ string. This type is incompatible with the expected param type of
  3: let multiply = function (a: number, b: number) {
                                 ^^^^^^ number

../../script.js:8
  8: multiply('1', '2');
                   ^^^ string. This type is incompatible with the expected param type of
  3: let multiply = function (a: number, b: number) {
                                            ^^^^^^ number

../../script.js:9
  9: multiply('qwe', 1);
              ^^^^^ string. This type is incompatible with the expected param type of
  3: let multiply = function (a: number, b: number) {
                                 ^^^^^^ number

Found 3 errors
// @flow

let multiply = function (a: number, b: number) {
    return a * b;
}

multiply(1, 2);
multiply('1', '2');
multiply('qwe', 1);

UPGRADED

$ ./cli.js
../../script.js:4
  4:     return a + b;
                ^^^^^ string. This type is incompatible with the expected return type of
  3: let sumDoNotEvenThinkAboutConcat = function (a, b):number {
                                                        ^^^^^^ number

../../script.js:4
  4:     return a + b;
                    ^ array literal. This type cannot be added to
  4:     return a + b;
                ^ number

../../script.js:4
  4:     return a + b;
                    ^ array literal. This type cannot be added to
  4:     return a + b;
                ^ string


Found 3 errors
// @flow

let sumDoNotEvenThinkAboutConcat = function (a, b): number {
    return a + b;
}

sumDoNotEvenThinkAboutConcat(1, 2);
sumDoNotEvenThinkAboutConcat('1', '2');
sumDoNotEvenThinkAboutConcat('qwe', [2]);
$ ./cli.js
flow is rechecking; this should not take long [merging inference] -../../script.
js:6
  6: array.push('qwe');
     ^^^^^^^^^^^^^^^^^ call of method `push`
  6: array.push('qwe');
                ^^^^^ string. This type is incompatible with
  3: let array: Array<number>;
                      ^^^^^^ number

../../script.js:7
  7: array.splice(1, 1, 'qwe');
     ^^^^^^^^^^^^^^^^^^^^^^^^^ call of method `splice`
  7: array.splice(1, 1, 'qwe');
                        ^^^^^ string. This type is incompatible with
  3: let array: Array<number>;
                      ^^^^^^ number

../../script.js:8
  8: array = ['1'];
              ^^^ string. This type is incompatible with
  3: let array: Array<number>;
                      ^^^^^^ number


Found 3 errors
// @flow

let array: Array<number>;

array = [1, 2 ,3];
array.push('qwe');
array.splice(1, 1, 'qwe');
array = ['1'];

You can notate:

  • Variable declarations
  • Function declarations
  • Class declarations
  • Object types
  • Function types
  • Array types
// variable declaration

var bar: number = 0,
    baz: boolean = true;

// function declaration

function numVowels(word: string): number {
  const vowels = new Set("aeiou");
  let count = 0;
  for (let char of word)
    if (vowels.has(char))
      count++;
  return count;
}

// Object types

type Person = {
  name: string,
  age: number,
};
// Array types

var array_of_num: number[] = [];
var array_of_num_alt: Array<number> = [];
var optional_array_of_num: ?number[] = null;
var array_of_optional_num: Array<?number> = [null, 0];


// class declaration

class Point {
  x: number;
  y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  move(x: number, y: number) {
    this.x += x;
    this.y += y;
  }

  copy(): Point {
    return new Point(this.x, this.y);
  }
}
$> npm install -g babel-cli
$> npm install --save-dev babel-plugin-transform-flow-strip-types
$> echo '{"plugins": ["transform-flow-strip-types"]}' > .babelrc
$> babel-node index.js
hello world!
// @flow

var str: number = 'hello world!';
console.log(str);

Type notations can be easily thrown away.

Getting started with flow.

$> mkdir -p get_started
$> cd get_started
$> echo '{"name": "get_started", "scripts": {"flow": "flow; test $? -eq 0 -o $? -eq 2"}}' > package.json
$> touch .flowconfig
$> npm install --save-dev flow-bin
// @flow


var str = 'hello world!';
console.log(str);

Add // @flow or /* @flow */ at the very beginning of your file

Thus you mark files which you want flow to check.

$> npm run-script flow

> test@ flow /get_started
> flow

No errors!
  • TypeScript and Flow have influenced each other heavily
  • Basic typings are pretty similar
  • Flow can catch common type errors without any type notations
  • Both also support React
  • Flow can even understand TypeScript declaration files
  • TypeScript is a compiler, Flow is a checker
  • Flow written in OCaml, Typescript in Typescript

Should I use Flow?

  • if your project does not live for long - no.
  • if your project is really simple - no.
  • if there is a chance you will need to refactor the code - yes.
  • if your system is very important - yes.
  • if people leave your team frequently - yes.

THE END

Flow type

By Nikita Rudy

Flow type

  • 141