TypeScript's type-independent output
Many questions about TypeScript's type system and implementation can be answered by understanding one of their central design goals: the output (generated JavaScript) is independent of the types found in the input. This seems simple enough but it has some surprising consequences. Here are some answers to questions you might have that are actually already answered by this principle.
Can a program with type errors still compile and produce JS? Yes,
because the output is the same no matter the types. (There's a
compiler flag, --noEmitOnError
to say "don't generate any output when
there's an error"; you might want to turn it on in your
tsconfig.json
for serious apps.)
The compiler can even generate something if the input has syntax errors though that is likely not what you want. Type some gibberish in the TypeScript playground to see.
Can plain JavaScript files without any type annotations be
transpiled by the compiler? Yes, because of the prior point. (You
have to opt in to allow the .js
extension.)
How do I make a run-time decision based on the underlying type of a
variable? E.g. if a function accepts some BaseClass
param, is
there a way to determine which subclass was actually passed? The
answer is no, unless you provide some method on BaseClass
yourself
or use typeof
/instanceof
, which only knows about the types that
JavaScript knows about. (This is true in any language, of course —
if the type is only known at run time, it must be a run-time type
query!)
However, see also the docs on type guards for some assistance from the compiler.
Are TypeScript union types the same as sum types? A TypeScript
union type is written like string|number
, which looks a lot like a
sum type as written in ML/Haskell. But a union type just means the
variable (or return type of a function, etc.) can be either of the
options, and determining which requires a typeof
/instanceof
check
to determine the run-time JavaScript type. In contrast, an ML-style
sum type has a type tag that e.g. requires you to pattern match to
extract the contained value.
(Why provide these weak-looking union types? Because TypeScript is attempting to model existing JavaScript usage, and many APIs such as jQuery semantically are already using union types. Also, as you'll see in a future post, these union types end up being pretty powerful.)
Can I use for (... of ...)
to iterate through ES6 maps? Because
the generated code is independent of types, when targeting ES5 output,
for-of loops always generate plain
for (var i = 0; i < x.length; i++) ...
loops. When targeting ES6 output, the output is the same as the input, a for-of loop. This means you can't use a for-of loop to iterate through an ES6 map when targeting ES5 output, which is pretty disappointing!
There are open bugs on this but the solution is not obvious. Perhaps the right thing would be to polyfill ES6 iterators and always use them.
How do overloaded functions work? TypeScript lets you write code with overloaded functions like this:
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: any, b: any): any { return a + b; }
However, because the output is the same regardless of the types, you already know that the first two type-only definitions are dropped in the output. You can only proivde a single definition of a function to JavaScript, and that function, which provides the actual implementation, must be compatible with all the different type signatures declared. (In practice, overloaded functions are mostly used to describe the types of existing JS libraries.)