Static type checking & JavaScript
Reasons for static type checking?
undefined is not a function
Reasons for static type checking?
1. Less bugs
Reasons for static type checking?
1. Less bugs
2. Safe refactorings
// types are X-RAY for code
function getUser(
id: number, <-- first arg is a number
options: {skipCache: boolean} <-- second arg is an object
): Promise<?User> { <-- returns a promise with either null or User
...
}
Reasons for static type checking?
1. Less bugs
2. Safe refactorings
3. Easier to maintain
let incomeByDay = [
{income: 12},
{income: 13}
]
incomeByDay.push({incme: 15})
let sum: number = incomeByDay.reduce(
(a, b) => a + b.income,
0
)
let incomeByDay = [
{income: 12},
{income: 13}
]
incomeByDay.push({incme: 15})
let sum: number = incomeByDay.reduce(
(a, b) => a + b.income,
0
)
TypeScript
FlowType
Closure Compiler
...
type User = {
name: string;
age: number;
}
let u: User = {name: 'John', age: 25}
function addition(a: number, b: number): number {
return a + b
}
TypeScript
written in TypeScript
compiles ES2015 to ES5*
no need for babel*
more syntax
feels like a language
FlowType
written in OCaml
just typechecks
you need babel
compat with JS tools
Type Systems
DX (Type inference, tooling, ...)
Soundness (correctness, ...)
Ecosystem
// Type inference in TypeScript
export function addition(a: number, b: number) {
return doAddition(a, b)
}
// can't infer argument types and complain :(
function doAddition(a, b) {
return a + b
}
// Type inference in FlowType
// need to annotate only exports
export function addition(a: number, b: number) {
return doAddition(a, b)
}
// infers argument and return types!
function doAddition(a: number, b: number): number {
return a + b
}
// "Soundness" in TypeScript
let promisedString: Promise<string> = new Promise(
resolve => {
// Perfectly valid in TypeScript!
resolve(42)
}
)
Flow is about control flow analysis (CFA)
// define.js
function fetchCurrentUser() {
return hasCurrentUser ? {name: 'John'} : null
}
// use.js
let user = fetchCurrentUser()
console.log(user.name)
if (user != null) {
// hm... we checked for != null so that must be safe to use "user"
console.log(user.name)
}
// Flow: objects
type User = {
name: string;
age: number;
}
let user: User = {
name: 'John',
age: 25,
phone: '555-55-55'
}
// Flow: unions
type MaybeLazyNumber =
| number
| () => number
function getNumber(n: MaybeLazyNumber): number {
if (typeof n === 'function') {
return n()
} else {
return n
}
}
getNumber(42)
getNumber(() => computeFactorial(42))
getNumber('oops')
// Flow: intersections
type Person = {
name: string;
}
type HasCat = {
theirCatName: string;
}
function hello(
person: Person & HasCat
) {
console.log(`${person.name} has cat named ${person.theirCatName}`)
}
// Flow: exact object types
type User = {| name: string; age: number; |}
let john: User = {name: 'John', age: 25}
john.phone = '555-55-55'
// Flow: mixed type
let user: mixed = JSON.parse('{"name": "John"}')
console.log(user.name)
if (
typeof user === 'object' &&
user != null &&
user.name != null
) {
console.log(user.name)
}
// Flow: validating external data
import {object, string} from 'validated/schema'
import {validate} from 'validated/object'
let schema = object({name: string})
let data: mixed = JSON.parse('{"name": "John"}')
let user: {name: string} = validate(schema, data)
console.log(user.name)
console.log(user.age)
// Flow: literal types (modeling enums)
type DownloadState =
| 'INIT'
| 'IN-PROGRESS'
| 'FINISHED'
| 'ERRORED'
let state: DownloadState = 'INPROGRESS'
// Flow "hacks": refinements
class IsInteger {}
export type Integer = number & IsInteger;
export function integer(a: number): ?Integer {
return a % 1 === 0 ? ((a: any): Integer) : null
}
// more at http://tinyurl.com/flow-refinements
// Flow "hacks": phantom types
class Validated {}
class Unvalidated {}
class Data<State> {
value: string
constructor(value: string) {
this.value = value
}
}
// Flow "hacks": phantom types (pt 2)
export function make(input: string): Data<Unvalidated> {
return new Data(input)
}
export function validate(data: Data<Unvalidated>): ?Data<Validated> {
if (!isValid(data)) {
throw new Error('data is not valid')
}
return (data: any)
}
function use(data: Data<Validated>): void {
console.log(‘using ‘ + data.value)
}
// more at http://tinyurl.com/flow-phantom-types
// Flow "hacks": immutability
type User = {
+name: string;
}
let u: User = {name: 'John'}
console.log(u.name)
u.name = 'Jack'
// more at http://tinyurl.com/flow-immut
OCaml / Reason + BuckleScript
PureScript
Ur / Web
Idris
Haskell + GHCJS
F# + Fable
Elm