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:
- 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:
- 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:
- Type Checking is not the main selling point of TypeScript.
- 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
- define types ("models")
- bind models to data
- 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
- 394