Flow

Facebook's static type-checker for JavaScript

 

Crosslead Engineering -- Nov. 6th, 2015

Plan

  • Motivation for Flow
  • Usage and Types
  • Gulp + Docker
  • Flow in the Crosslead Platform

Motivation

Javascript is has a very flexible relationship with null values and type coercion

Subtitle

> a.definitelyHasThis.method()
TypeError: Cannot call method 'method' of undefined
    at repl:1:22
    at REPLServer.self.eval (repl.js:110:21)
    at Interface.<anonymous> (repl.js:239:12)
    at Interface.emit (events.js:95:17)
    at Interface._onLine (readline.js:203:10)
    at Interface._line (readline.js:532:8)
    at Interface._ttyWrite (readline.js:761:14)
    at ReadStream.onkeypress (readline.js:100:10)
    at ReadStream.emit (events.js:98:17)
    at emitKey (readline.js:1096:12)
> function multiply(a, b) { return a * b; }
undefined
> multiply('a', 'b')
NaN

The goal of Flow is to find errors in JavaScript code with little programmer effort. Flow relies heavily on type inference to find type errors even when the program has not been annotated - it precisely tracks the types of variables as they flow through the program.

From the horses mouth...

/* @flow */

function foo(x) {
  return x * 10;
}

foo('Hello, world!');
hello.js:7
  7: foo("Hello, world!");
     ^^^^^^^^^^^^^^^^^^^^ function call
  4:   return x*10;
              ^ string. This type is incompatible with
  4:   return x*10;
              ^^^^ number

Flow's type checking is opt-in: files are not type checked unless you ask it to.

// no flow comment, no more errors!

function foo(x) {
  return x * 10;
}

foo('Hello, world!');

Flow How-To

  • built in types
  • constructing type aliases
  • annotating your code
  • interfaces

Available Types

(primatives)

any      // -> Describes any possible type
boolean  // -> Describes true or false values
mixed    // -> Describes the supertype of all types
number   // -> Describes literal number values
string   // -> Describes literal string values
void     // -> Describes undefined values

Available Types

(Built-in Class Types)

Array<T>   // -> Describes Array objects with elements of type T
Boolean    // -> Describes Boolean objects (but not boolean literal values!)
Class<T>   // -> Describes the type of the class that would instantiate a "T" instance
Function   // -> Describes any function
Number     // -> Describes Number objects (but not number literal values!)
Object     // -> Describes any object
String     // -> Describes String objects (but not string literal values!)
Promise<T> // -> Describes Promise resolving to type T

Available Types

(Signature Types)

// Object Literal Signature Type
{ prop1: number, prop2: string } // two specific properties
{ [key: string]: string }        // only string values, any keys...

// Function Signature
(p: string, x: number) => number

// callable object signature
{ (p: string): number, prop: number }

// Tuple
[number, string, number]

Available Types

(Algebraic Types)

// type unions
type myPrimative = string | number | boolean

// type intersections
type fooBarFn = ((x: Foo) => void) & ((x: Bar) => void)

Type Declarations

(building your own types!)

// lets make a binary tree type!

// the binary tree type is parametric to the type of its leaves "T"
type binTree<T> = {
  left?:  binTree<T> | T,
  right?: binTree<T> | T
}

// this typechecks ok!
const tree: binTree<number> = {
  left: 1,
  right: {
    left: 1
  }
};


// this does not!
const tree: binTree<number> = {
  left: 1,
  right: {
    left: 's' // string does not match the given type parameter
  }
};

Type Declarations

(More examples...)

type stringOrNumber = string | number;

type nodeCallback = (error?: any, data: any) => void;


//
// a few types used in app/global/common/ScoreToggle.js
//
type Score = { [key: string]: number | Score };

type Node = {
  _id:      string,
  keyPath?: string,
  _rId?:    string,
  scores?:  Score,
  parent?:  Node
};

type NodeList  = Array<Node>;
type ScoreMap  = { [key: string]: Score };
/* @flow */

function foo(x) {
  return x * 10;
}

foo('Hello, world!');

Add a comment to start typechecking a file...

// variable 
var a: number = 1;

// class
class A {
  
  prop: number // instance property annotation
  static sProp: string // static property annotation

  constructor(a: number) { // no type annotation needed on constructor
    this.prop = a;
  }

  method(): A { // method annotation, returning class A as type
    return this;
  }
}

// function parameter and return type annotation
async function getTypes(organizationId: string): Promise<Array<string>> {
  const types = await get(`api/communications/types/${organizationId}`) || [];
  return types;
}

// parametric polymorphism (or templates / generics)
function makeTuple<T>(a: T, b: T): Array<T> {
  return [ a, b ];
}

Type Annotations

declare class _D3_ELEMENT_ {
  select(key: string): _D3_ELEMENT_;
  selectAll(key: string): _D3_ELEMENT_;
  attr(config: { [key: string]: any }):  _D3_ELEMENT_;
  attr(key: string, value: any): _D3_ELEMENT_;
  attr(key: string):  any;
  style(config: { [key: string]: any }):  _D3_ELEMENT_;
  style(key: string, value: any): _D3_ELEMENT_;
  style(key: string): any;
}

declare class _D3_ {
  select(key: string): _D3_ELEMENT_;
  selectAll(key: string): _D3_ELEMENT_;
  format(spec: string): Function;
  scale(): Function;
  svg(): Function;
  time(): Function;
  extent(data: Array<any>, accessor?: Function): Array<any>;
  values(obj: {[key: string | number]: any }): Array<any>;
  range(size: number): Array<number>;
}

declare var d3: _D3_;

Interface Files

Actually Running Flow

(Gulp + Docker)

app/flowdist/.flowconfig
[ignore]
.*node_modules/phantomjs.*

[include]
../node_modules/*

[libs]
../interfaces/

[options]
suppress_comment= \\(.\\|\n\\)*\\$FlowIssue

Getting Flow to

accept our code

Babel

Flow

Source

Dist

Getting Flow to

accept our code

  es7.comprehensions
  es7.classProperties
  es7.doExpressions
  es7.functionBind
  es7.trailingFunctionCommas
  es7.objectRestSpread
  es7.decorators
  es6.properties.computed

ES6/ES7 support: 

https://github.com/facebook/flow/issues/560

Whitelisted Transformers:

Steps to run flow locally

(OSX)

 

  • Install flow (v0.17.0)
  • run `gulp flow`

Steps to run flow locally

(windows)

 

  • Install docker
  • start an instance of a flow docker container using the following command:
    • docker run --rm -it -v %cd%:/app motiz88/flow:0.17.0 bash
    • (this will start a bash prompt within the linux environment, with your current directory mounted at /app)
  • In a separate terminal window (on your local windows environment) run `gulp flow --nocheck` to start a watch task to compile for flow.
  • Back in the docker prompt, cd to `./flowdist` and run `flow`

Usage in development of the crosslead platform

app/global/* 

 is currently passing flow!

Examples

Flow

By Ben Southgate

Flow

  • 852