Flow vs TypeScript

Type Systems for JavaScript

Nordic Coding 2016

Oliver Zeigermann / @DJCordhose

Slides: http://bit.ly/types-nordic-coding

Why using type systems?

type systems make code easier to maintain

type annotations

  • can make code more readable
  • can make code easier to analyse
  • can allow for reliable refactoring
  • can allow for generally better IDE support
  • can catch some (type related) errors early

Anders Hejlsberg@Build2016: Big JavaScript codebases tend to become "read-only".

http://stateofjs.com

Recently published survey on the state of JavaScript

http://stateofjs.com/2016/flavors/

TypeScript

ease of use and tool support over soundness

  • http://www.typescriptlang.org/
  • By Microsoft (Anders Hejlsberg)
  • Based on ES6 (probably ES7/ES8)
  • Adds optional type annotations, visibility, and decorators
  • Compiler checks and removes annotations
  • 2.x with major changes released recently

Flow

soundness, no runtime exceptions as goal

  • http://flowtype.org/
  • By Facebook
  • Flow is a static type checker, designed to quickly find errors in JavaScript applications
  • Not a compiler, but checker
  • If present, type annotations can very easily be removed by babel for runtime

Basics

TypeScript

let obj: string;
obj = 'yo';
// Error: Type 'number' is not assignable to type 'string'.
obj = 10;


// types can be inferred (return type)
function sayIt(what: string) {
    return `Saying: ${what}`;
}
const said: string = sayIt(obj);


class Sayer {
    // mandatory
    what: string;

    constructor(what: string) {
        this.what = what;
    }

    // return type if you want to
    sayIt(): string {
        return `Saying: ${this.what}`;
    }
}

Flow


let obj: string;
obj = 'yo';
// Error: number: This type is incompatible with string
obj = 10;


function sayIt(what: string) {
    return `Saying: ${what}`;
}
const said: string = sayIt(obj);


class Sayer {
    what: string;

    constructor(what: string) {
        this.what = what;
    }

    sayIt(): string {
        return `Saying: ${this.what}`;
    }
}

Right, pretty much the same

Those basic features help with documentation, refactoring, and IDE support

Nullability

Flow


function foo(num: number) {
    if (num > 10) {
        return 'cool';
    }
}

console.log(foo(100).toString());

// error: call of method `toString`.
// Method cannot be called on possibly null value
console.log(foo(100).toString());

// to fix this, we need to check the result
const fooed: string|void = foo(100);
if (fooed) {
    fooed.toString();
}

Types are non-nullable by default in flow

TypeScript


function foo(num: number) {
    if (num > 10) {
        return 'cool';
    }
}

// same as flow
const fooed: string|void = foo(100);
if (fooed) {
    fooed.toString();
}

// or tell the compiler we know better (in this case we actually do)
fooed!.toString();

Only applies to TypeScript 2.x

Only works when strictNullChecks option is checked

All types nullable by default in TypeScript 1.x

`any` type

can be anything, not specified

can selectively disable type checking


function func(a: any) {
    return a + 5;
}

// cool
let r1: string = func(10);

// cool
let r2: boolean = func('wat');
  • flow: explicit any supported, but any never inferred
  • TypeScript: explicit any supported
  • TypeScript 1.x: any inferred if no type specified and no direct inference possible
  • TypeScript 2.x: inference much smarter

Generic Type information

Types can be parameterized by others

Most common with collection types


let cats: Array<Cat> = []; // can only contain cats
let animals: Array<Animal> = []; // can only contain animals


// nope, no cat
cats.push(10);
           

// nope, no cat
cats.push(new Animal('Fido'));
           

// cool, is a cat
cats.push(new Cat('Purry'));
           

// cool, cat is a sub type of animal
animals.push(new Cat('Purry'));
           

Up to this point this pretty much works in Flow and TypeScript the same way ...

Differences in Generic Types

  • TypeScript
    • parametric types are compatible if the type to assign from has a more special type parameter
    • seems most intuitive, allows for obviously wrong code, though
  • Flow
    • using generic types you can choose from invariant (exact match), covariant (more special), and contravariant (more common)
    • Array in Flow has an invariant parametric type
    • more expressive, harder to master

Both Flow and TypeScript support upper, but not lower bounds

Are Flow and TypeScript like Java/C++/C#?

Not really

  • Both
    • optionally typed / any
    • built to match common JavaScript programming patterns
    • type systems more expressive
    • type inference
    • control flow based type analysis
  • TypeScript
    • semantically compatible with JavaScript
  • Flow
    • just a checker
    • not even a language of its own
    • non-nullability as default

Structural vs Nominal Typing

  • Nominal Typing: types are compatibility when their declared types match
  • Structural Typing: types are compatibility when their structures match
  • Java, C#, C++, C all use nominal typing exclusively
  • Flow classes are also treated as nominal types
  • TypeScript classes are treated as structural types
  • Everything else in both Flow and TypeScript uses structural typing

Nominal Typing for Flow classes


class Person {
    name: string;
}

class Dog {
    name: string;
}

let dog: Dog = new Dog();

// nope, nominal type compatibility violated
let person: Person = dog; // ERROR: Dog: This type is incompatible with Person

// same problem
let person: Person = { // ERROR: object literal: This type is incompatible with Person
    name: "Olli"
};
            

Structural Typing for TypeScript classes


class Person {
    name: string;
}

class Dog {
    name: string;
}

let dog: Dog = new Dog();

// yes, correct, as structurally compatible
let person: Person = dog;

// same thing, also correct
let person: Person = {
    name: "Olli"
};

            

Structural Typing for both TypeScript and Flow


// this is fine as nominal typing only applies to Flow classes
let namedObject: NamedObject = dog;

// same thing, also fine
let namedObject: NamedObject = {
    name: "Olli"
};

// not fine in either
let namedObject: NamedObject = {
    firstName: "Olli"
};

// cool in flow, but TypeScript wants perfect match with object literal
// ERROR: Object literal may only specify known properties,
// and 'firstName' does not exist in type 'NamedObject'.
let namedObject: NamedObject = {
    name: "Olli",
    firstName: "Olli"
};
            

Classes in TypeScript

TypeScript has special support for classes

Makes it easier for people coming from Java/C++/C#

Flow does not feature those or any other syntactic sugar, as it is a checker only

Integrations of raw JavaScript files

TypeScript Declaration files

  • Core Declarations come with TypeScript compiler
  • Needs External Type Declarations for 3rd party libraries
  • Managed by Typings tools (typings install dt~mocha --save)
  • Will be made obsolete by npm (npm install @types/react --save)
  • If there are no existing declaration files

3rd Party Libraries in Flow

IDE Support

Visual Studio Code

Atom / Nuclide

IntelliJ IDEA / Webstorm

Starting from 2016.3

Demo

Some hacking in Webstorm

Where do they excel?

  • TypeScript: supporting people from Java and C# land
    • more complete IDE support
    • language server
    • large set of 3rd party declaration files
  • Flow: proving types for idiomatic JavaScript
    • very easy to get started even with existing project
    • more powerful and flexible generics
    • better inference (TypeScript 2.x might catch up)
    • nominal typing for classes

Wrap-Up

  • TypeScript and Flow have influenced each other heavily
  • Basic typings are pretty similar
  • Both also support React
  • Many more constructs like union, intersection, and array types in both
  • TypeScript is a compiler, Flow is a checker
  • Flow shoots for soundness, TypeScript for tool support
  • Flow has non-nullable types as defaults
  • Generics in TypeScript are easier, but less expressive
  • Flow's type system is generally more expressive
  • Flow is written in OCaml, Typescript in Typescript
  • Integration with existing 3rd party libraries a PITA in TypeScript 1.x (improved in TypeScript 2.x)

Thank you!

Questions / Discussion

Oliver Zeigermann / @DJCordhose

http://bit.ly/types-nordic-coding

Resources