TypeScript: Industrial-strength JavaScript

19.01.2015
Historians who reflect on JavaScript's emergence as a dominant programming language in the 21st century may find themselves quoting Donald Rumsfeld: "You go to war with the army you have, not the army you might wish to have."

For growing numbers of programmers, JavaScript is the army we have. As we send it into the field to tackle ever more ambitious engagements both client and server side, we find ourselves battling the language itself.

JavaScript was never intended for large programs built by teams that use sophisticated tools to manage complex communication among internal modules and external libraries. Such teams have long preferred strongly typed languages like Java and C#. But those languages' virtual machines never found a  home in the browser. It was inevitable that JavaScript alternatives and enhancements would target the ubiquitous JavaScript virtual machine.

Among these is TypeScript, the brainchild of Anders Hejlsberg. I would not have predicted that the creator of Turbo Pascal and C# would, in 2015, be working in the open on GitHub, for Microsoft, to strengthen the Web's native programming language. But it's a delightful outcome. TypeScript is an important project that delivers useful benefits now and is moving forward nicely.

The TypeScript language is most succinctly described as an optionally typed superset of JavaScript. Since existing JavaScript is valid TypeScript, you can begin using the TypeScript compiler -- and TypeScript-aware tools -- by simply changing filename extensions from .js to .ts.

That turns out to be a good way to start. TypeScript can be immediately useful, even before you add any type annotations to your own code, thanks to the type definitions collected at definitelytyped.org. The definitions available there, for hundreds of JavaScript libraries (including jQuery, Angular, and Bootstrap), describe the interfaces exported by those libraries. TypeScript-aware tools use those definitions to flow type awareness into TypeScript programs that import the annotated libraries.

That same kind of type awareness is also available, in a portable way, for JavaScript's core library and for the browsers document object model (DOM). It's portable because TypeScript is written in TypeScript, targets JavaScript VMs on any platform, and provides not only a compiler but also a set of language services that support code intelligence. Visual Studio is one consumer of those services. Others include WebStorm, Eclipse, and Sublime Text. There's also the TypeScript playground, an interactive Web page that completes statements, prompts for parameters and their types, and warns about type errors.

Second time's the charm

When I took my first run at TypeScript in 2013, I wanted to stick with the language but didn't for a few reasons. The compiler was a bit slow back then, and a delay of even a few seconds frustrates a JavaScript developer accustomed to instant results. Type definitions were scarcer and less mature. Plus, some basic JavaScript idioms required workarounds.

When I tried again in late 2014 I found none of those problems. But my experience was also better, I think, because I approached tasks in a more incremental way.

The first time around, I immediately began refactoring my code to take advantage of TypeScript's support for classes. That's one of the features of the forthcoming ECMAScript 6 that TypeScript makes available now. You can express object-oriented idioms in tomorrow's JavaScript syntax and use the compiler to produce the less convenient and less readable idioms that work in the browser today. In retrospect, the top-down approach wasn't the best way to get started.

The second time around I took a bottom-up approach. I started adding type annotations to otherwise unaltered JavaScript code. As I did so, my grasp of the code improved. Even though mine wasn't a large program, JavaScript's implicit conversion among strings, numbers, and dates had made it hard to visualize the types of objects that flowed into and out of functions. Of course, you can document those expectations in comments, but without strong tool support, such documentation drifts away from the reality of the code. 

With TypeScript that documentation lives as part of the code during the development process, and enables an IDE -- in my case Visual Studio -- to bring the documentation to life. But it's all a convenient illusion. When you compile to JavaScript, the annotations fall away. If you've done no more than add such annotations, the resulting JavaScript will exactly match your original unannotated code. (If you have restructured the code to use classes and modules, you can use the JavaScript source map the compiler emits for TypeScript-source-level debugging in browsers and stand-alone debuggers.)

"Instead of having a switch that turns types on and off," says Anders Hejlsberg, "we have a dial." You can invest incremental effort for incremental reward. As you add annotations, you improve your own ability to reason about the code, and you enable tools to provide more powerful automated support for that reasoning.

There's also a multiplier effect because the TypeScript compiler works hard to infer types where it can. If you tell it that a function returns a string, it will know that a variable holding the result of that function is a string, even if you haven't annotated the variable with the string type. If you later try to assign it a number, the compiler will complain.

Once I'd checked all my primitive types I began writing simple classes to model JavaScript objects made from primitive types. As I added type annotations for these compound objects, Visual Studio made their members available for code completion. Functions that received -- or returned -- those objects as parameters became self-describing and self-checking. Again this code awareness flowed through the program. In JavaScript you can't know, a priori, whether the return value you're assigning to a variable is of the type you expect. That uncertainty results in much confusion and error. Why tolerate it

There's a test for that

The growing popularity of dynamically typed languages like JavaScript happened to coincide with a growing appreciation for the practice of writing self-testing software. As a result, there's been some tendency to conflate the two trends. If your test coverage is sufficiently robust, some say, there's no reason to incur the overhead of static types.

We can debate the feasibility of writing tests that obviate the need for static typing. But if you buy the argument that type safety becomes important at large scale, TypeScript invites you to consider how you want to invest your testing effort.

Tests are, after all, another kind of overhead: more code to write and maintain. Arguably what can be tested mechanically should be. If computers can figure out whether two software objects are compatible, humans ought to delegate that job to them. Writing tests, like writing the software those tests exercise, is a creative act that should require and benefit from human intelligence. Automatic type checking is, from this point of view, a way to free up human creativity for higher purposes. 

One of those higher purposes is effective naming. "There are only two hard things in computer science," it's famously said, "cache invalidation and naming things." The names of variables, functions, classes, and modules are the most fundamental kind of documentation.

It's hard enough to coin names that usefully describe their roles in a program. It's even harder to adjust those names as those roles evolve during the ongoing development of a program. That's partly because naming anything well -- in software or in any other domain -- is intrinsically hard. But in software there are special risks when names that appear in different contexts, and mean different things, can't easily be recognized as such. That's the situation in JavaScript. There are a number of incompatible workarounds for organizing code into modules, but the language itself doesn't support any of them.

ECMAScript 6 takes a major step forward. It provides a standard way to organize programs, which may spread across many files, as sets of modules. That mechanism, which TypeScript adopts, is a boon for large-scale development. When module dependencies are declared in a standard way, programmers can more readily understand those dependencies, tools can automate that understanding, and code refactoring becomes less risky.

For example, in a conventional JavaScript environment it's risky to rename a module-scoped or class-scoped variable, function, or class that's referenced in many places, perhaps from many files, because there's no way to know if the name is intended to mean something else in another context. A TypeScript-aware IDE knows and can make such refactoring a safe operation.

Culture shock

Despite its advantages, TypeScript faces an uphill climb for reasons that are more cultural than technical. Developers prize JavaScript's freedom of expression, and while TypeScript works hard to be unobtrusive, its mission requires developers to voluntarily give up some of that freedom.

The benefits become clear at large scale, but many JavaScript projects aren't operating at that scale. One that is, Mozilla's Shumway, implements a Flash-compatible runtime in more than 170,000 lines of TypeScript. It's easy to appreciate why a project of that size would need industrial-strength type checking. For many projects, though, the need doesn't seem so compelling.

Then there's the Microsoft factor. Its rocky relationship with open Web technologies over the years has left many Web developers with a sour taste that won't easily dissipate.

TypeScript is, objectively, doing everything right. The project is being developed in the open on GitHub, under an Apache 2.0 license, and it accepts community contributions. The vast majority of pull requests are from the GitHub accounts of Microsoft employees on the core team, naturally, but you can also find community-contributed bug fixes. This is not the behavior people have learned to expect from Microsoft, and it will take more than a project like TypeScript to change those expectations.

What has turned some heads is TypeScript's relationship with a couple of other projects with similar goals: Google's AtScript (which is being used to build Angular 2.0) and Facebook's Flow. AtScript is exploring the idea of runtime as well as static type checking, while Flow hopes that aggressive type inference can minimize the need to annotate existing code bases and simplify the use of type checking for new development. Both evaluated TypeScript, and neither adopted it. But everyone agrees that type checking for large-scale JavaScript development is an idea whose time has come. 

More accurate, it's an idea whose time has come back around again. TypeScript's type annotation syntax originated in ECMAScript 4 and was implemented in ActionScript 3, but never landed in the ECMAScript 5 that runs in today's browsers. Type annotations aren't part of the forthcoming ECMAScript 6, but are widely expected to land in the next incarnation of the language. Nobody knows what those annotations will be. But AtScript and Flow are betting that they'll look a lot like the TypeScript syntax, so they're aligning with it.

When JavaScript expert Axel Rauschmayer wrote about TypeScript, AtScript, and Flow on his blog, the first comment was: "Hmm, when Facebook, Google, and Microsoft are working together it kinda looks like future of the Web." It does indeed. That future has been a long time in the making, and will unfold gradually, but the reinforcements we need are on the way.

(www.infoworld.com)

Jon Udell