Growing popularity of TypeScript revived an old static vs dynamic languages battles. When you’re engaged in it, you want yourself to be prepared. With TypeScript it’s not that simple as many people think, so we will make a short dive into the basics of programming languages theory. Let’s start with things seeming obvious.

Dynamic typing — the type is associated with the value, and checked at run-time.

function g( a ) {

return a / 10;

}

Static typing — type is associated with variable or textual expression, and checked at compile-time.

function g( a : number ) : number{

return a / 10;

}

That’s dead simple, one would wonder why we even discussing it. Every kiddo knows this stuff, really. But wait. Before we will be engaged into bloody static vs dynamic stuff battle, we will dig into the topic a bit further. Cause there’s something interesting and confusing at the same time right behind the surface.

Static types, type annotations, and type inference

So, the matter of static typing is these type annotations. You add them to your program… And because of that, it becomes statically typed. Like here:

function g( a : number ) : number{

return a / 10;

}

Right?

Nah, of course it’s not :). It’s much weirder than that. :)

If you carefully look at the program above, you’ll notice that in this case types can be determined from the context. g() can return number only, and, to operate correctly, it must take number as an argument.

“Type inference “ — types can be determined from the context, making type annotations optional.

Thus, smart static type system with type inference capabilities would allow you to omit type annotations in this example and still perform a complete static type check. And your code will look like the one below and it will still be statically typed code:

function g( a ) {

return a / 10;

}

What? But… It looks just the same as the dynamic one!

Yeah, it does. :) To give you some particular examples of real-world statically typed languages with such a semantic, I can mention ML-family programming languages and Haskell. They are based on Hindley-Milner type system, which guarantees that types will be extracted from the context and statically checked. Which means that…

You can write the whole program without the single type annotation, and it will be statically typed program.

So, what exactly makes language static or dynamic? Type annotations? No. Not even close.

Soft type system and gradual typing

Aren’t you confused enough already? Because I forgot to add that there are dynamic languages around with type annotations capabilities as well. Just to note few of them capable of making static type checks — it’s Dialyzer tool for Erlang/Elixir, and Google Closure Compiler for JavaScript.

To make things clear about the TypeScript and to obtain some peace of mind, let’s open TypeScript language playground, and try out our example there:

When you start typing g() function call, you’ll see the function signature which is inferred by the TypeScript compiler from the context (type inference!). Interesting detail is that the type of “a” is not a number. It’s inferred as any. And the reasons why it happens are very important.

No, it’s not because TypeScript’s type inference is incapable. Thing is, that despite the fact that there are both type annotations and type inference, it is still dynamic language. It still assumes that you can call g() with any argument type, and its valid situation when it returns NaN. Cool, eh?

Finally, what exactly makes it dynamic? It’s not just an opportunity to make optional type annotations; many statically typed languages offers the same thing. It’s the presence of any type and the major difference in the way how type inference works. It prefers ‘any’ type to ‘number’, thus it’s dynamic by default as regular JavaScript is. When you want it to be stricter, you have to add type annotation. Not vice versa.

This mix of the type annotations with “any” type is called Gradual typing. When it prefers “any” by default, it’s called “Soft type system”. Not exactly an opposite to dynamic, nor to static typing, but taking the best of both.

Every good thing you can say about dynamic nature of JavaScript (if any, it depends on what camp you are from) is applicable to the TypeScript too. It’s not the subject of ‘static vs dynamic’ warfare. There are no any ‘opposite sides’, and it’s not about forcing you doing things you hate or don’t like to do. It’s about new opportunities.

Now, assuming that you’re coming with dynamic languages background, let’s make the brief overview of these opportunities type annotations and static type checks can give you, and some of common misconceptions about it. For example, you may heard that…

Static type checks reduces the amount of defects in your code?

If you take it literally (as many people do), it’s wrong statement. Just think a bit. An amount of defect in your code at the end entirely depends on your QA procedures. It will find all important errors anyway no matter which programming language you’re using. You may write in assembly. Still, your defects won’t pass QA.

Thing is, that developer injects more or less the same amount of defects, and static type checks allow him to notice and fix the fraction of these defects earlier, and thus with a less effort.

While it’s good, this thing itself is entirely not the point. Not an important one, at least. Static type checks in the majority of the languages (and TypeScript is not an exception) are too weak to be seriously competitive with the rest of QA procedures. Nothing to talk about.

So, what’s the point then? Why people tell that TypeScript is the tool large system developers are long awaited for? Because…

Type annotations aided with type checks helps you to do system decomposition

What would you do, when you deal with a something which is complex? You will likely try to split it apart, to the set of isolated parts with clean interfaces. Thus, you may concentrate on the single part, and don’t bother yourself remembering an implementation details of the other parts, as far as you know their interface.

Type annotations gives you the formal language to describe interfaces between the parts

You can describe these interfaces in plain English, of course. This idea is not bad by itself, but there’s the catch. When you make changes to the system (and this is an essence of our work), you are supposed to keep your docs in sync.

And guess what? People usually don’t do this. As result, you just cannot trust plain English specs, no matter how wonderful they seems at the first glance.

I always make beautiful docs, and I always keep them in sync!

Even assuming you’re really belongs to that 1% (no doubt you are). Still, I don’t trust you. Nothing personal, it’s just because you’re a human. And humans are so unreliable… (How could you really guarantee that your docs actually conforms to the code? Are you always in the mood? Do you always have time and no excuses? Don’t you make any mistakes? Are you working on it alone, or there’s a whole team of superheroes in charge?)

Software development team prepares for daily documentation update.

Type annotations is the documentation which is checked by compiler against itself and the code; it’s guaranteed to be correct and up-to date.

Type annotations are the sort of technical documentation which can be trusted. Docs which tell you that you don’t conform with the spec. No superheroes are required. Period.

And being a formal language, type annotations actually allow you to express and communicate the design in very efficient way. Which is invaluable tool when you’re dealing with something big and messy, especially in a team.

After all, as Kenneth Iverson noted in his Turing lecture, the notation is not just the notation.

Notation is the tool of thought.

And if there’s a language giving you words to express a design, it gives you the opportunity to efficiently think about design. And this is, in fact, the most important point at all.