Flow

and static types for JavaScript

Frontend Weeklies, 2014-12-05

Background

  • We are sloppy programmers
  • We work with weak types and language quirks
  • We rely on tools and conventions to keep our sanity
  • We hate writing documentation

Maintaining large JavaScript codebases takes effort

  • Lots of stupid unit tests
  • Extensive code reviews
  • Meticulous focus on conventions

Static Types

some options

Dart

  • OO
  • Optional static types
  • Checked/production mode to toggle runtime checking
  • Generics
  • Compile to JS, run in Dartium, or use in Dart VM
  • Futures, Streams
  • async/await (also coming to ES7, see Scala/C# for examples)
import 'dart:io';
import 'dart:async';

void printDailyNewsDigest() {
  File file = new File("dailyNewsDigest.txt");
  Future future = file.readAsString();
  future.then((content) {
    print(content);
  });
}

www.dartlang.org/docs/tutorials/futures/

AtScript

  • Coming to AngularJS 2.0
  • Type annotations
  • Metadata annotations
  • Runtime type checking (DI needs it)
  • Compile to ES5 or Dart
@Component({selector: 'foo'})
class MyComponent {
  @Inject()
  constructor(server:Server) {}
}

www.2ality.com/2014/10/typed-javascript.html

TypeScript

  • JavaScript superset (aiming to be superset of ES6 in 2.0)
  • (Optional) type annotations
  • Generics
  • Interfaces/structural types
  • OO stuff
  • Compile time type checking
interface Person {
    firstname: string;
    lastname: string;
}

function greeter(person : Person) {
    return "Hello, " + person.firstname + " " + person.lastname;
}

var user = {firstname: "Jane", lastname: "User"};

document.body.innerHTML = greeter(user);

www.typescriptlang.org/Tutorial

.d.ts files

declare module Rx {
  export interface Observable<T> {
    (dueTime: Date, scheduler?: IScheduler): Observable<T>;
    delay(dueTime: number, scheduler?: IScheduler): Observable<T>;
    throttle(dueTime: number, scheduler?: IScheduler): Observable<T>;
    timeInterval(scheduler?: IScheduler): Observable<TimeInterval<T>>;
    timestamp(scheduler?: IScheduler): Observable<Timestamp<T>>;
    sample(interval: number, scheduler?: IScheduler): Observable<T>;
    sample<TSample>(sampler: Observable<TSample>, scheduler?: IScheduler): Observable<T>;
    timeout(dueTime: Date, other?: Observable<T>, scheduler?: IScheduler): Observable<T>;
    timeout(dueTime: number, other?: Observable<T>, scheduler?: IScheduler): Observable<T>;
  }
}

definitelytyped.org

"However, in practice, we notice that developers often—for 50% of all variables—use the `any` type, thus making TypeScript dynamic and weak"

baishakhir.github.io/uploads/fse2014-lang_study.pdf

Flow

  • JavaScript type checker
  • Built in OCaml (Steve Yegge must be smiling)
  • Built to scale to large codebases
  • Focus on type inference
  • Gradual type system
  • jsx compiler used for type erasure (i.e. only compile time type checking)
  • Attempts to be close to the normal JS workflow
/* @flow */

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

foo('Hello, world!');
$> cd flow/examples/01_HelloWorld
$> flow check
01_HelloWorld/hello.js:7:5,17: string
This type is incompatible with
  01_HelloWorld/hello.js:4:10,13: number

Flow

  • Optional (except in module exports) TypeScript type annotations
  • Tuples
  • Structural types
  • Subtyping (extends, prototypes)
  • Polymorphic classes and functions (generics)

Flow

  • Union types
  • Maybe types (nullable types)
  • Declarations (not compatible with .d.ts files ATM)
  • Supports React's propTypes

Generics

function id(x) {
  return x;
}

Generics

function id<T>(x: T): T {
  return x;
}

See Parametricity by @dibblego for an explanation why generic functions are awesome

Maybe Types

/* @flow */
var o = null;
print(o.x);
file.js:3:7,9: property x
Property cannot be accessed on possibly null value
  file.js:2:9,12: null

Maybe Types

/* @flow */

function length(x) {
  return x.length;
}

var total = length('Hello') + length(null);
03_Null/nulls.js:4:10,17: property length
Property cannot be accessed on possibly null value
  03_Null/nulls.js:7:38,41: null

Maybe Types

/* @flow */

function length(x) {
  if (x !== null) {
    return x.length;
  } else {
    return 0;
  }
}

var total = length('Hello') + length(null);

Example: ES6 Promises

The API

declare class Promise {
    constructor(callback);
    then(onFulfill, onReject);
    catch(onReject);
    static cast(object);
    static resolve(object);
    static reject(error);
    static all(promises);
    static race(promises);
}

Let's add some annotations

Generics

declare class Promise<R> {
    constructor(callback): void; // void instead of Promise<R> in Flow core?
    then<U>(onFulfill, onReject): Promise<U>;
}

Functions

declare class Promise<R> {
    constructor(callback: (resolve: (result: R) => void,
                           reject: (error: any) => void) => void): void;
                           // why any instead of Error in Flow core?

    then<U>(onFulfill, onReject): Promise<U>;
}

Union types

declare class Promise<R> {
    constructor(callback: (resolve: (result: R) => void,
                           reject: (error: any) => void) => void): void;

    then<U>(onFulfill?: (value: R) => Promise<U> | U,
            onReject?: (error: any) => Promise<U> | U): Promise<U>;
}

Note map vs. monadic bind (flatMap) in the then handler!

Full annotated API

declare class Promise<R> {
    constructor(callback: (resolve: (result: R) => void,
                           reject: (error: any) => void) => void): void;

    then<U>(onFulfill?: (value: R) => Promise<U> | U,
            onReject?: (error: any) => Promise<U> | U): Promise<U>;

    catch<U>(onReject?: (error: any) => U): Promise<U>;
    static cast<T>(object?: T): Promise<T>;
    static resolve<T>(object?: T): Promise<T>;
    static reject(error?: any): Promise<any>;
    static all<T>(promises: Array<Promise<T>>): Promise<Array<T>>;
    static race<T>(promises: Array<Promise<T>>): Promise<T>;
}

github.com/facebook/flow/blob/master/lib/core.js

Summary

  • Early warnings are good
  • Static types help in maintaining and refactoring code
  • Up-to-date documentation
  • Avoiding stupid unit tests
  • Good type inference is crucial for adoption

Future

  • Linting taken to the next level
  • Optional type annotations are considered for ES7, but introducing them is very difficult
  • AtScript, TypeScript, and Flow developers are working together \o/

Resources

Thanks!

@kpuputti