Javascript type systems - the surprising parts

Abhishek Yadav

for JsFoo-2018

1.

The null type

The null

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,

  • Flow doesn't have this problem
  • --strictNullChecks

 

The null

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.

 

2.

Structural typing

Types in JS

Same shape -> same type

Yay !

Structural types

// 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

Structural types

// 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.

3.

Sealed objects

Types in JS

Objects get sealed. 

Sealed - not immutable

 🤔

Sealed objects

// 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

Sealed objects

// 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

 

4.

Type widening

Types in JS

Flow allows wider objects to be assigned to smaller ones

Wider - has more properties

 

Okkkkay ... but why ?

Type widening

// 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.

 

 

Type widening

// 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

5.

Type Variance

Type variance

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      √

Type variance

Type variance

Types in JS

// TODO - Typescript example

Flow

Sound.

Arguments: contravariant

Return: covariant

 

Typescript:

Not sound

Argument: Bivariant

6.

The List.head

The List.head

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)

javascript-type-systems

By Abhishek Yadav

javascript-type-systems

  • 1,311