I’ve been “noodling around” with Typescript for a while, and enjoying the various improvements over standard Javascript.
Quite a bit of this comes from ES6 improvements which Typescript incorporates (and transforms to sensible ES3 output) - for example
real classes, and this
-capturing lambda expressions that help avoid the #1 kick-yourself error of Javascript stupidity.
Syntactic sugar aside, there are a few reasons I like Typescript. An improved IDE experience in VS (and VS2012 is already quite good with intellisense via jsdoc comments and JS execution). Static typing improves the self-documenting nature of code, helps enforce a cleaner design, and ensure program correctness. Type definition files are available (the DefinitelyTyped project maintains a good selection) for popular JS libraries to allow their use with full type information in a Typescript program (and this interoperability is huge).
In this post I’m going to discuss some improvements in the recent TypeScript 0.9 and 0.9.1 which get us closer to this goal.
noImplicitAny
Javascript has some weird and wonderful features which make layering static typing over it somewhat hard. On top of the bizarre oddities that no sane programmer would expect, the dynamic nature of the language make some things hard to statically type.
There are 2 ways to approach features which cannot be typed in an existing version of the type system, one is to refrain from using them in order to improve your program’s maintainability, ease of understanding, correctness etc (the lint-style approach of using “the good parts”), the other is to enhance the type system to be more expressive.
Typescript’s any
type covers all the cases where the type of an expression can be basically anything at runtime. This allows for the awkward
cases where the type system is not powerful enough to express what the programmer knows - an escape clause. It also functions as the type
where standard Javascript code is present without type annotations (which are optional):
When developing Typescript code with the intention of using full type annotations and achieving type safety, this implicit ‘any’ typing can be more of a nuisance than a help. In fact, since Typescript has type inference this becomes rather hard to spot - you don’t need type annotations everywhere, just wherever the type system is not able to infer the correct type. But you might assume that a type is inferred where in fact it is just implicitly ‘any’, and then you have the misapprehension of type safety without any of the guarantees.
Typescript 0.9.1 introduces the --noImplicitAny
flag to disallow such programs. Then the above program will not compile:
any.ts(23,12): error TS7006: Parameter 'n' of 'f' implicitly has an 'any' type. any.ts(23,15): error TS7006: Parameter 'm' of 'f' implicitly has an 'any' type.
Generics
Functional languages have had the notion of “polymorphism” for a long time - this is when data structures and functions can operate over any type. Often this is used with lists, for example this F# function works to repeat an element of any type into a list of repetitions of that item:
Here 'a
is a type variable, standing for any type (and 'a list
is a list of things of that type). This is in the F# core
as List.replicate
.
In contrast to this “parametric polymorphism”, object oriented languages have long provided a different feature of “subtype polymorphism”. This is classically useful to treat your button and textbox alike as widgets to compose your UI (and many other things). One obvious thing that this does not help with is the case of homogeneous collections, and so the mainstream OO languages (C++, Java, C#) now (eventually!) all have a form of “generics” which allow us to express strongly typed operations over arbitrary types (“generics” and “[parametric] polymorphism” are terminology for the same thing from different worlds). For example, the above in C#:
(this is a naive version of LINQ’s Enumerable.Repeat).
Now to Typescript. As Javascript does not have static typing, one can easily write functions which are “morally generic”. Here’s a definition of a similar repeat function in Typescript:
Note that without knowing the type of x
, we’ve assigned it the any
type, and as a result the function returns any[]
(if we omit type annotations on x
and l
they would be implicitly any
to the same result).
Typescript 0.9 introduces generics syntax which should be familiar as above, and we have:
And this function returns T[]
.
This might all seem a bit academic, but I think it’s huge. As an example take the library
Underscore.js. This provides
a bunch of utility functions for working on lists and collections (familiar to the functional programmer/LINQ user,
and some of which are in some JS implementations but not present on all browsers).
Prior to 0.9, many types would incorporate any
, e.g. rest
which returns the rest of a list after skipping the first
(or first n) element(s):
Now this can return the same type of array:
Knockout
Knockout has the notion of observable properties - to create such a property we wrap it in a call to
ko.observable
, e.g.
The pre-generic typing of knockout had some fixed overloads for built in types number
, string
, but if you use
a user-defined type, you were cast back into any
-land. Now with generics we can have an Observable<T>
type.
Note, I did not specify the type of the observable. If I run tsc -d
on the above input I get the resulting types inferred:
The difference between these two typings is that the following mistake will be rejected by the generic typing:
Conclusion
The addition of generics to Typescript probably marks the point where if you’re thinking of trying it, it’s worth
doing so, particularly if your favourite JS libraries have typing definitions available
(again, check DefinitelyTyped). There are a number of other improvements
in these recent releases (0.9 /
0.9.1) - overloading on constants is also
quite funky. It’s also worth trying --noImplicitAny
, though you might well decide it’s not for you.