http://djcordhose.github.io/flow-vs-typescript/flow-typescript-2.html
Oliver Zeigermann / @DJCordhose
More extensive version including Elm: http://bit.ly/js-types
type annotations
Anders Hejlsberg@Build2016: Big JavaScript codebases tend to become "read-only".
Recently published survey on the state of JavaScript
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}`;
}
}
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}`;
}
}
Those basic features help with documentation, refactoring, and IDE support
function foo(num: number) {
if (num > 10) {
return 'cool';
}
}
// cool
const result: string = foo(100);
console.log(result.toString());
// still cool?
console.log(foo(1).toString());
// error at runtime
"Cannot read property 'toString' of undefined"
TypeScript does not catch this
function foo(num: number) {
if (num > 10) {
return 'cool';
}
}
// error: call of method `toString`.
// Method cannot be called on possibly null value
console.log(foo(100).toString());
Flow does catch this
But why?
The inferred type is something else
// error: return undefined. This type is incompatible with string
function foo(num: number): string {
if (num > 10) {
return 'cool';
}
}
// nullable type: the one inferred
function foo(num: number): string | void {
if (num > 10) {
return 'cool';
}
}
// to fix this, we need to check the result
const fooed: string|void = foo(100);
if (fooed) {
fooed.toString();
}
function foo(num: number) {
if (num > 10) {
return 'cool';
}
}
// to fix this, we need to check the result
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();
Important: Only works when strictNullChecks option is checked
More Improvements in TypeScript 2.0
Catches up with Flow on control flow analysis (needed to make null checks really useful)
Types are nullable by default in TypeScript 1.x
Non-nullable are still possible in TypeScript 1.x in a limited way
Types are non-nullable by default in Flow
TypeScript 2.x makes types non-nullable by default using `strictNullChecks` in `tsconfig.json`
Neither Flow nor TypeScript 2.0 catch this
class Person {
name: string;
constructor() {
// why no error?
}
}
const olli: Person = new Person();
// issues error as expected
const daniel: Person = {
};
At least in TypeScript, this won't be fixed: https://github.com/Microsoft/TypeScript/issues/8476
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog extends Animal {
// just to make this different from cat
goodBoyFactor: number;
}
class Cat extends Animal {
purrFactor: number;
}
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'));
let cats: Array<Cat> = []; // can only contain cats
let animals: Array<Animal> = []; // can only contain animals
// error TS2322: Type 'Animal[]' is not assignable to type 'Cat[]'.
// Type 'Animal' is not assignable to type 'Cat'.
// Property 'purrFactor' is missing in type 'Animal'.
cats = animals;
// wow, works, but is no longer safe
animals = cats;
// because those are now all cool
animals.push(new Dog('Brutus'));
animals.push(new Animal('Twinky'));
// ouch:
cats.forEach(cat => console.log(`Cat: ${cat.name}`));
// Cat: Purry
// Cat: Brutus
// Cat: Twinky
TypeScript allows for birds and dogs to be cats here :)
let cats: Array<Cat> = []; // can only contain cats
let animals: Array<Animal> = []; // can only contain animals
// ERROR
// property `purrFactor` of Cat. Property not found in Animal
cats = animals;
// same ERROR
animals = cats;
End of story for Flow
Both Flow and TypeScript support upper, not lower bounds
Both Flow and TypeScript support F-Bounded Polymorphism
https://flowtype.org/blog/2015/03/12/Bounded-Polymorphism.html
https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#type-parameters-as-constraints
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"
};
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"
};
// 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"
};
TypeScript has special support for classes
Similar features can be found in Java/C++/C#
Flow does not feature those or any other syntactic sugar, as it is a checker only
Starting from 2016.3
My recommendation
If so, what might be the problem here?
List(tests).sort((t1, t2) => t1.id - t2.id)
Those ids
actually are strings, result always is NaN
// this would have saved you
type Test = {
id: string;
// ...
};
// error 'string' This type is incompatible with 'number'
List(tests).sort((t1, t2) => t1.id - t2.id)
Oliver Zeigermann / @DJCordhose
http://djcordhose.github.io/flow-vs-typescript/flow-typescript-2.html