In this blog post, I describe the pros and cons of three programming languages/dialects: JavaScript, TypeScript and ReasonML. My descriptions are based on recent experiences with TypeScript and ReasonML on a few smaller real-world projects and on years of experience with JavaScript.

Before we take a look at the languages, let’s first consider the pros and cons of static typing, given that TypeScript and ReasonML are statically typed and JavaScript isn’t.

The pros and cons of static typing #

Pros:

Documentation: For most code, I find it very helpful if the types of parameters are documented. Then I know what to expect as a caller and as a callee. But it goes further than that. When I revisited an old JavaScript code base to add static types, I had forgotten how it worked. With the types, it is now laid out much more clearly how everything works. For example, in order to add type annotations for the parameters of a function, I often had to visit its call sites. That is, the information “how is this function used?” is hinted at by the types. I’d also consider auto-completion in editors as “better documentation”. It means you have to consult docs much less when working with an API (good doc comments help, too). For example, when I started doing more DOM programming in 2006, I did it via GWT (which is based on Java and was an attractive solution back then, for several reasons). Exploring and learning the DOM API was fun, due to auto-completion in Eclipse.

A quick way to check the types of parameters. I knew I was ready to try static typing in JavaScript once I added programmatic type checks to several functions – that code felt like unnecessary boilerplate.

It helps with refactoring (adding cases to enums, etc.).

Cons:

It takes time to learn.

It is an additional layer of complexity. You are basically writing the code again, on a different level.

It constrains your freedom of expression. Things can get complicated if you get into generics, co- and contra-variance (e.g., an Array of strings is not a subtype of an Array of objects), etc.

It does not prevent defects. At least that’s what current research says. In my experience, you do catch a certain class of errors (e.g. missing null checks). But I may have caught them even earlier, if I had written unit tests instead of adding static types. In order to detect serious defects, you need tests.

You lose some interactivity and compiling takes time. On the other hand, it’s become almost impossible to avoid compilation in the JavaScript ecosystem.

If you want to experience the best of what static typing has to offer, use ReasonML (or OCaml, which it is based on). For example, almost every language feature is designed so that you need the least amount of type annotations (by supporting type inference).

It is impressive, how far along everything already is (especially the editor support), but several important pieces are still being worked on: better support for Promises, iteration and async iteration; an improved standard library called Belt; improved JavaScript interop (which is already decent, but still more complicated than I’d like); better support for Unicode strings.

ReasonML has impressively short build times. It’s considerably faster than TypeScript, which is a definite usability advantage.

It has some bindings for JavaScript libraries, but the selection is still limited: see Reason Package Index.

You have the option to go native. For example, Jared Forsyth has written the game Gravitron in ReasonML that runs natively on Android, iOS, web and macOS.

For more information on ReasonML, consult my book “Exploring ReasonML and functional programming” (free to read online).

TypeScript’s type system is more lightweight than I expected it to be. It feels more like FP than like Java. For example, it works structurally and not nominally: If you create an interface, that interface “matches” all objects whose shape is described by that interface. You can introduce interfaces at any time, without having to touch existing code.

The type system is also quite powerful and intuitive. You can statically type a lot of idiomatic JavaScript, thanks to union types, intersection types, discriminated union types, etc.

Editor support (via Visual Studio Code, WebStorm, etc.) is outstanding.

Many npm packages either come with static type definitions or have external ones that are dead-simple to install. Consult DefinitelyTyped for more information.

Naturally, JavaScript interop is excellent. With one exception: simulating named parameters via object literals is unnecessarily complicated to type statically (more information).

Conclusion: TypeScript occupies a nice middle ground between JavaScript and ReasonML. I’d be interested in hearing how well it does for large projects.

The whole ecosystem is a constant source of innovation and experimentation (Babel, etc.).

You have the broadest selection of compatible libraries, via npm.

You can move as quickly as possible and keep everything as dynamic as you want.

Have the option to explore and create code without a build step.

For more information on JavaScript language features, consult my series of books, “Exploring JS”, which is free to read online.

Conclusion: the JavaScript ecosystem is stronger than ever #

Static typing or not is an emotional topic. My advice is:

Use whatever makes you happy and productive.

Do acknowledge both strengths and weaknesses of what you are using.

Personally, I’ve started to use some kind of static typing once a project grows beyond a certain size (or if I expect it to eventually grow that big).

The strength and variety of the JavaScript ecosystem is amazing at the moment: You can switch between JavaScript, ReasonML and TypeScript, as needed (and with varying degrees of work). They share some tools, many libraries, and much syntax. For example, when I needed a quick templating solution in ReasonML, I used the JavaScript-based EJS library, via npm.

Lastly, note that there are many other good options out there: the static type checker Flow, the functional programming language Elm, etc.