Strong Dynamic
Type Checking
for JavaScript

Sylvain Pollet-Villard

DevRel at Worldline

@SylvainPV

hello FOSDEM !

Strong type-checking ?

Here are two commonly accepted definitions:


1. an explicit binding between a variable and a type that

guarantees this type always describe correctly the referenced data

 

variable

type

2. no lack of type safety due to looser typing rules,
     for example
implicit type coercion

Strong and dynamic ?

Sounds like oil and water

image credits:  Kolja Wilcke

var hi = "Hello !"
hi = 83110
hi = ["H","e","l","l",0]
hi = () => alert("Hello !")

JavaScript being dynamically typed, variables can change types during program execution which makes
types very unpredictable

The lack of strong type-checking is still the best source of memes for JS

TypeScript joins the party

Optional static type-checking for JavaScript
and a few other features: interfaces, decorators...
 

10 years of constant exponential growth

problem solved ?

Things I learned about TypeScript after 10 years:

  1. Type Checking is not the main selling point of TypeScript.
    It's Developer Experience.

API exploration

Docs inside IDE

Autocomplete

Code generation

Type Inference

Compile-time

optimizations

Easy Refactoring

Typo Detection

Things I learned about TypeScript after 10 years:

  1. Type Checking is not the main selling point of TypeScript.
    It's Developer Experience.

Running TypeScript compiler is now considered a loss of time

Things I learned about TypeScript after 10 years:

  1. Type Checking is not the main selling point of TypeScript.
  2. Type safety can be easily defeated.

Unpredictable stuff in front-end web dev:
(unpredictable = known at run-time)

Back-end & 3rd party server responses

Browser API
(& bugs & quirks)

Client-side stored data

User inputs

Where TypeScript is helpless

Hey TypeScript,
how do I type-check all of this, please ?

Sorry, not a compiler problem,
more an applicative problem

You'll have to deal with it by yourself

Let's be honest
The usual way we deal with runtime errors
is to not really deal with them

if(!isValid(data)){ /* try to fix stuff */ }

window.onerror = pretendWeCareAboutUnexpectedErrors
try { } catch(error){
  alert(`Something bad happened: ${error}`)
}

"the ability to bind an explicit type to a variable and guarantee that this type always describe correctly the referenced data."

Strong dynamic type-checking

What if we could do this dynamically at runtime ?

  • Type errors would still be runtime errors,
    but more explicit and catched early for easier debugging
     
  • More than just a one-time validation pass, types shall be revalidated on every reassignment or mutation
     
  • This should help to get rid of silent errors, and make runtime errors much more predictable and manageable

variable

type

  • my most "used for real" OS project so far
     
  • also the hardest to code
    (8 years old, rewritten 4 times)

Strong Dynamically Typed Object Modeling for JavaScript

  1. define types ("models")



     
  2. bind models to data



     
  3. your data is now type-safe

How to use it

How it works

class User {
  constructor(name, age){
    if(typeof name !== "string")        
      throw new TypeError("Invalid type for name")
    if(typeof age !== "number")
       throw new TypeError("Invalid type for age")
        
    this.name = name
    this.age = age
  }
}

const joe = new User("Joe", 13);
joe.age = "twelve"
// does not throw any error

How it works

class User {
  constructor(name, age){
    const proxy = new Proxy(this, {
      set(obj, key, value){
        if(key === "name" && typeof value !== "string") 
          throw new TypeError("Invalid type for name")
        if(key === "age" && typeof value !== "number") 
          throw new TypeError("Invalid type for age")
        
        return Reflect.set(obj, key, value)
      }
    })
    proxy.name = name
    proxy.age = age
    return proxy
  }
}

const joe = new User("Joe", 13);
joe.age = "twelve"
// TypeError: Invalid type for age

How it works

function typecheck(obj, definition){
  return new Proxy(this, {
    set(obj, key, value){
      if(key in definition && typeof value !== definition[key])
        throw new TypeError("Invalid type for " + key)

      return Reflect.set(obj, key, value)
    }
  })
} 

class User {
  constructor(){
    return typecheck(this, {
      name: "string",
      age: "number"
    })
  }
}

const joe = new User();
joe.age = "twelve"
// TypeError: Invalid type for age

Types or Models ?

What's the difference ?

  • Models can easily check the values, not only their types

 

 

 

  • Models can test objects with complex assertions

 

 

 

  • Model validation can affect application logic
const Shirt = new ObjectModel({
  // valid values: 38, 42, "S", "M", "L", "XL", "XXL"...
  size: [Number, "M", /^X{0,2}[SL]$/]
})
const Integer = Model(Number).assert(Number.isInteger)
const PositiveInteger = Integer.extend().assert(
  function isPositive(n){ return n >= 0 }
)
Shirt.errorCollector = (err) => logError(err) && cancelOrder()

ObjectModel pros

  • Descriptive error messages,
    pointing to the initial problem, not its consequences
  • Continuous data validation as part of the dev process: faster debugging, more reliable applications
  • Global or per-feature strategies for runtime error management

ObjectModel cons

  • Performance penalty on data with very frequent mutations
    (avoid on data mutating more than 100 times per second)
  • Require browser support for ES6 Proxies:
    (Edge 14+, Firefox 47+, Chrome 50+, Safari 10+, Node 6.0+)

Which is better ?
Static or dynamic ?

USE BOTH

My recommendation:

Static anotations
in core logic

Dynamic modeling
on external interfaces

they address different issues

that's all folks

thanks !

Sylvain Pollet-Villard

@SylvainPV

Strong Dynamic Type Checking for JavaScript - FOSDEM 2023

By sylvainpv

Strong Dynamic Type Checking for JavaScript - FOSDEM 2023

  • 425