Update 2014-11-18: Facebook Flow has been released as open source. Its website is flowtype.org. The site mentions plans for Flow’s future.
This blog post looks at three initiatives for adding static typing to JavaScript: Microsoft’s TypeScript, Facebook’s Flow and Google’s AtScript.
Let’s first clarify some terminology related to typing (excerpted from “Speaking JavaScript”).
In the context of language semantics and type systems, static usually means “at compile time” or “without running a program,” while dynamic means “at runtime.”
In a statically typed language, variables, parameters, and members of objects (JavaScript calls them properties) have types that the compiler knows at compile time. The compiler can use that information to perform type checks and to optimize the compiled code.
Even in statically typed languages, a variable also has a dynamic type, the type of the variable’s value at a given point at runtime. The dynamic type can differ from the static type. For example (Java):
Object foo = "abc";
The static type of foo is Object
; its dynamic type is String
.
Normal JavaScript is dynamically typed; types of variables are generally not known at compile time.
Does JavaScript really need static typing? Aren’t unit tests enough? Static typing isn’t magic: it does add complexity and visual clutter. This is especially true if you need to manually specify a type for everything (like in most of Java). Thankfully, neither of the three variants of typed JavaScript mentioned in this post force you to do so: if you don’t explicitly type an entity, they try to infer its type, by how the entity is used. Three examples:
number
.number
.Static typing offers the following benefits:
One more advantage is political: static typing (complemented by classes and modules) makes JavaScript more palatable for programmers coming from static languages such as Java and C# (i.e., many enterprise programmers). There is a danger of those programmers missing some of the subtleties of JavaScript, because it looks too familiar. However, more people liking a language that is still very recognizably JavaScript is a win, in my opinion.
TypeScript [1] is a subset of ECMAScript 6 [2] plus optional static typing. There are several ECMAScript 6 (ES6) features it doesn’t support yet (e.g. let
, destructuring, string templates, promises, iterators and for-of
loops) and it still uses an older version of the ES6 module syntax [3]. Both divergences from ES6 will disappear by version 2.0 [4], meaning that TypeScript will be a strict superset of ES6 (give or take ES6 features that are difficult to compile to ES5, such as generators and proxies).
As mentioned, static typing is optional in TypeScript and supported via:
Type annotations: You can annotate parameters, function results and properties to declare their types.
Generics: TypeScript supports generic type variables, generic types, generic classes and generic constraints.
Interfaces: enable you to describe the structure of a value. Interfaces match structurally (“duck typing”). Therefore, you can introduce interfaces for “existing” values – externally and without changing how those values are created (no need to “implement” like in Java or C#).
Visibility modifiers for instance properties declared in classes. If a property is marked private, it can only be accessed from “within” a class, external accesses produce compiler errors.
Let’s look at examples.
Type annotations. In the following code, the parameters x
and y
and the function results are declared to have the type number
. Thus, the compiler shows an error for the function call in the last line.
function add(x : number, y : number) : number {
return x + y;
}
add('a', 'b'); // compiler error
TypeScript compiles the function to the following ECMAScript 5 code. That is, all type information is gone at runtime, the end result is normal JavaScript.
function add(x, y) {
return x + y;
}
Interfaces. In the following code, we demand that objects passed via the parameter x
must have the number
-valued property length
.
function getLength(x : { length: number }) {
return x.length;
}
console.log(getLength('abcd')); // 4
console.log(getLength(['a', 'b', 'c'])); // 3
Visibility modifiers. In the following code, x
and y
are private properties of Point
instances. They can be accessed by the method dist()
, but not from outside.
class Point {
constructor(private x, private y) {
}
dist() {
return Math.sqrt(this.x*this.x + this.y*this.y);
}
}
var p = new Point(3, 4);
console.log(p.x); // compiler error: Point.x is inaccessible
console.log(p.dist()); // 5
The class Point
is translated to this ECMAScript 5 code.
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.dist = function () {
return Math.sqrt(this.x * this.x + this.y * this.y);
};
.d.ts
files TypeScript allows you to provide static type information for existing (untyped) code via external files, which have the file name extension .d.ts
. The website DefinitelyTyped provides such files for many libraries. For example: jQuery, Backbone.js, Esprima, Express, gulp and Jasmine. That means that working with those libraries becomes more convenient if an IDE supports TypeScript. IDEs that do so are: Visual Studio, WebStorm, Eclipse (via TypEcs) and others.
As an example (one of several in the TypeScript manual) let’s assume that this is how the animalFactory
API is used:
animalFactory.create("dog");
animalFactory.create("giraffe", { name: "ronald" });
animalFactory.create("panda", { name: "bob", height: 400 });
// Invalid: name must be provided if options is given
animalFactory.create("cat", { height: 32 });
The .d.ts
file for this API would be:
interface AnimalOptions {
name: string;
height?: number;
weight?: number;
}
function create(name: string, animalOptions?: AnimalOptions): Animal;
Flow [5] is a type checker for ECMAScript 6 that is based on flow analysis. As such, it only adds optional type annotations to the language and infers and checks types. It does not help with compiling ECMAScript 6 to ECMAScript 5. Flow is already in use at Facebook and will be open-sourced “later this year”.
React lets you use Flow’s annotations by removing them while compiling its JavaScript dialect to plain JavaScript. Quoting the React Blog:
And lastly, on the heels of announcing Flow at @Scale yesterday, we're adding the ability to strip TypeScript-like type annotations as part of the
jsx
transform. To use, simply use the--strip-types
flag on the command line, or setstripTypes
in the options object when calling the API.
The Flow type system has support for extensible objects, open methods, prototypes, tuples, enums, generics, nullable types, union types, intersection types and more. All of these features are motivated by staying true to JavaScript’s nature, by the desire to capture how current JavaScript code is (implicitly) typed. Nullable types help prevent type errors caused by accessing properties if a value is undefined
or null
. You explicity specify whether, for example, a given parameter can be null
(or undefined
) or not. In the former case, the compiler forces you to check for null
every time you access the parameter. In the latter case, the compiler warns you if you pass null
or a nullable value.
Why not TypeScript? Flow’s type annotation syntax being compatible with TypeScript’s makes you wonder why Facebook doesn’t use TypeScript. Avik Chaudhuri mentioned [5:1] the following reasons:
In order to scale, Flow runs as a server in the background and keeps itself up to date. Tools query the server. Flow’s type analysis is incremental, meaning that it can check modules in isolation. It supports many ECMAScript 6 features such as arrows, destructuring, optional parameters, promises, etc. Facebook is committed to track JavaScript standards as they evolve.
The preferred way to code AngularJS 2.0 will be AtScript [6]. It is compiled to ECMAScript 5 (for now) and all of the AngularJS 2 features will be accessible from ES5 code.
AtScript is ECMAScript 6 plus the following extensions:
In contrast to TypeScript and Flow, both data is available at runtime.
You can turn type annotations into runtime type checks [6:1]: The following is AtScript code.
class MyClass {
methodA(name : string) : int {
var length : int = name.length;
return length;
}
}
The above code is equivalent to this ECMAScript 6 code:
import * as rtts from 'rtts';
class MyClass {
methodA(name) {
rtts.types(name, rtts.string);
var length = tts.type(name.length, rtts.int);
return rtts.returnType(length, rtts.int);
}
}
The idea is to use runtime type checks during development, because they help catch errors when you are working with untyped code. For deployment, you wouldn’t insert them into the code.
Meta-data annotations mean that data is attached to annotated entities. For example, the following code uses two annotations, @Component
and @Inject
.
@Component({selector: 'foo'})
class MyComponent {
@Inject()
constructor(server:Server) {}
}
It is translated to:
class MyComponent {
constructor(server) {}
}
MyComponent.parameters = [{is:Server}];
MyComponent.annotate = [
new Component({selector: 'foo'}),
new Inject()
];
AngularJS uses the runtime type information for dependency injection and to configure constructs such as directives. For example [7]:
@Directive({
selector: ['[blink]']
})
class Blink {
constructor(element:Element,
options:Options,
timeout:Timeout) {
var selectors:Array<CssSelectors> =
someThirdPartyAPI();
element.query(selectors.join(','))
.forEach(e => options.apply(e));
}
}
Thus, while TypeScript and Flow throw type data away after compilation, AtScript keeps it around. This is the only way to make it available to runtime mechanisms such as dependency injection. It also enables you to type-check JSON you load at runtime.
What surprised me is that AtScript (.ats
files) can be compiled to two target languages:
.js
files), via Traceur [2:1], which supports AtScript’s language extensions.dart
files)Given that AngularJS 2.0 is written completely in AtScript that means that there will be a single code base for both JavaScript and Dart.
The teams of TypeScript, Flow and AtScript seem eager to collaborate. Avik Chaudhuri says so in his talk [5:2] and the TypeScript team mentions it on their blog [4:1]:
The TypeScript team is working with both the Flow and AtScript teams to help ensure that resources that have already been created by the JavaScript typing community can be used across these tools. [...] In the long term, we will also be working to fold the best features of these tools into ECMAScript, the standard behind JavaScript.
Furthermore, the following timeline is given [7:1] for AtScript’s features (without mentioning specific dates for each step):
.js
and .dart
Obviously, steps 5 and 6 are somewhat beyond the control of the AtScript team and depend on how things develop in the future.
“Welcome to TypeScript”, the official TypeScript homepage ↩︎
“TypeScript and the Road to 2.0” by Jonathan Turner for the TypeScript Blog ↩︎ ↩︎
Video: “JavaScript Testing and Static Type Systems at Scale” by Avik Chaudhuri and Jeff Morrison ↩︎ ↩︎ ↩︎
“AtScript Primer” by Miško Hevery ↩︎ ↩︎
Slides: “Keynote: AtScript” from the ng-europe conference ↩︎ ↩︎