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