This is a stage 0 proposal to add a pluggable type system to JavaScript.
A pluggable type system is a set of syntactical constructs that serve as type annotations that formally bind them to an optional type checker (i.e. no assertions made at runtime) defined in userland (e.g. typescript, flow and closure). For example:
/*
* @types https://www.typescriptlang.org/
*/
function add(x: number, y: number): number {
return x + y;
}
At runtime, this is equivalent to:
function add(x /*: number */, y /*: number */) /*: number */ {
return x + y;
}
You bind a type system plugin to code by using a to-be-determined-at-stage-2 convention (alternatives).
Motivation
In the last 10 years, large engineering teams have developed type systems for JavaScript through a variety of preprocessors, transpilers and code editors to scale large codebases.
Most notably, TypeScript, Flow and the Closure have gained a massive amount of adoption and are used as the foundation and starting point of this proposal.
Unfortunately, the javascript syntax isn't expressive enough to enable an effective type checker, so it was extended beyond the standard language.
This proposal tries to bridge the gap between the standard javascript language and the massive benefits of type checkers, making them more accessible to users that want to remain within the boundaries of the standard javascript language, while still nurturing the innovation and development of independent type systems.
It accomplishes this by introducing a pluggable type system for JS composed of (a) syntax extensions and (b) a binding mechanism that enables (existing) type checkers to operate during code development according to the semantics that were bound.
/**
* A Shape represents objects that can ...
*
* @types http://flowtype.org/
*/
interface Shape {
...
}
At runtime, the syntax extensions are not used for type checking by interpreters.
/**
* An Account represents the user's ...
*
* @types https://developers.google.com/closure
*/
class Account {
constructor(initially: number) {
this.balance = initially;
}
deposit(credit: number) : number {
this.balance += credit;
return this.balance;
}
}
Pluggable type systems enable the standard javascript language to:
- delegate the development/innovation of type semantics to userland, formalizing current norms
- steer convergence on syntax of type systems for JS
- place a stepping stone on a possible path towards finding a unified optional type system
Browsers could, potentially, connect with a language server and offer type checks during development time (e.g. possibly even catching runtime type checking information).
Having said that, the main drawback with pluggable type systems for TC39 it that they corner ourselves from gradual typing (i.e. once introduced without runtime type checking, they can't add them afterwards without introducing extra syntax).
We believe, however, that there is significant evidence in the research literature that gradual typing incurs a significant performance penalty, which makes it impractical. In addition, TypeScript, Flow, and Closure Compiler have demonstrated that optional typing works well for JS at scale.
Strawman proposal
In this proposal, we describe syntactical extensions that enables type plugins to define semantics. At runtime, every extension below is semantically sequivalent to wrapping them in /* */
comments.
Binding
We believe it is important to make an association between the syntax and the type checking semantics to be performed. Currently, to the best of our knowledge, each type system has their own convention:
- file extensions (e.g.
.flow
or.ts
). - reserved
// @
comment annotations (e.g.// @flow
source annotation). jsdocs
annotations."use pragmas"
pragmas.
We should take these (and more) into consideration:
- does it require TC39 to enumerate/register type systems or can it be self-service?
- does the binding happen in the source code or out of band (e.g. compilation flag, file extensions, etc)?
As a starting point, consider a reserved jsdoc annotation (e.g. @types
) pointing to a URL (along the lines of xml namespaces) which is responsible to define how types should be interpreted.
For example:
/**
* This class abstracts X, Y and Z.
*
* @types https://www.typescriptlang.org/
*/
Syntax
In addition to the binding mechanism, we propose syntactical extensions for type annotations.
Variables
let isDone: boolean = false;
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let color: string = "blue";
let list: number[] = [1, 2, 3];
let x: [string, number] = ["hello", 10]; // tuples
let notsure: any = 4;
let u: undefined = undefined;
let n: null = null;
Functions
function add(x: number, y: number): number {
return x + y;
}
let mul: (x: number, y: number) => number =
function(x: number, y: number): number { return x * y; };
function vote(candidate: string, callback: (result: string) => any) {
// ...
}
Optional parameters:
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + " " + lastName;
} else {
return firstName;
}
}
Classes
class BankAccount {
balance: number;
constructor(initially: number) {
this.balance = initially;
}
deposit(credit: number) {
this.balance += credit;
return this.balance;
}
}
Interfaces
interface Timer {
currentTime: Date;
setTime(d: Date);
}
class Clock implements Timer {
...
}
Generics
interface Array<T> {
reverse(): T[];
sort(compareFn?: (a: T, b: T) => number): T[];
// ...
}
function identity<T>(arg: T): T {
return arg;
}
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}