Abhishek Yadav
for JsFoo-2018
Types in JS
let a: string = "a string"
a = null; // <- No type-fail in Typescript
function foo(x):string {
return undefined // <- No error. Not cool !
}
Typescript considers null a subtype of all types.
In other words, null/undefined can be assigned to a variable of any type
Thankfully,
Types in JS
let a: string = "a string"
a = null; // <- No type-fail in Typescript
function foo(x):string {
return undefined // <- No error. Not cool !
}
The rationale -
Ease of use.
Converting existing code bases gradually is much easier with this.
Types in JS
Same shape -> same type
Yay !
// Typescript
// Flow has similar behaviour
class A {
x: string
}
interface B {
x: string;
}
let b: B = new A(); // Cool !
The alternative is nominal types - stick to the declared names. Common in class based OOP languages
But Javascript too has classes now
Types in JS
Flow allow nominal types with classes.
o_O
// Flow
class A {
x: string
}
class B {
x: string;
}
let b: B = new A(); // <- Error
The Rationale
"Imagine two component classes that both have render() methods, these components could still have totally different purposes, but in a structural type system they’d be considered exactly the same.”
Types in JS
Objects get sealed.
Sealed - not immutable
🤔
// Typescript
// Flow behaves the same
interface A {
x: number;
y: string;
}
let a1: A = { x:1, y: "str" };
a1.z = true; // Error: Property z doesn't exist on A
Looks kind of obvious here -> we are accessing a property that doesn't exist
But are undeclared objects spared ?
Types in JS
No
// Typescript
// Flow behaves the same
let b = { x:1, y: "str" };
b.z = true; // Same Error
b.x = 234; // Its not immutable
Undeclared objects also get a type inferred.
And that type is also sealed
Types in JS
Flow allows wider objects to be assigned to smaller ones
Wider - has more properties
Okkkkay ... but why ?
// only in Flow
type A = { x: number }
let a:A = { x: 123, y: 'abc' } // <-- Okay
Well one is, you cant go wrong this way. The type system will make sure you use only the properties from the declared (narrower type)
And also, wider objects can be considered a sub-type of the narrower one.
Types in JS
But thats also not without problems
Type-widening and union types don't play nice together.
// Flow
type A = { x: number }
type B = { y: number }
type C = A | B;
let c:C = { x: 1, y: 1 } // Okay
let d = c.x // Error: x is missing in B
let e = c.y // Error: y is missing in A
//---------------------------------------------------
type A1 = {| x: number |}
type A2 = {| y: number |}
type C = A | B
let c:C = { x: 1, y: 2 } // Error right here
Fixed using the exact object type
Types in JS
Helps decide if two function types are compatible
Subtypes | Supertypes | |
---|---|---|
Covariance | Yes | No |
Contravariance | No | Yes |
Bivariance | Yes | Yes |
Types in JS
In sound type systems,
function arguments should be contravariant,
and return values should be covariant
In other words,
a type safe function
can accept supertypes
and can return subtypes
F = (Car => Car) => string
G1 = (Sedan => Sedan) => string x
G2 = (Sedan => Car) => string √
G3 = (Car => Sedan) => string x
G4 = (Car => Car) => string √
Types in JS
// TODO - Typescript example
Flow
Sound.
Arguments: contravariant
Return: covariant
Typescript:
Not sound
Argument: Bivariant
Types in JS
function shamelesslyFine(): string {
let arr = [];
return arr[0]; // <- No error in Typescript.
// <- No error in Flow.
}
List.head should always have some kind of Maybe type
But failing this didn't seem pragmatic to the designers
Flow does not do this because it would be extremely inconvenient to use. You would be forced to refine the type of every value you get when accessing an array.
(emphasis mine)