About this ‘A Language a Day’ Advent Calendar
This article became a part of my book ‘A Language a Day’, which you can get in both electronic and paper format. Languages covered in the book: C++, Clojure, Crystal, D, Dart, Elixir, Factor, Go, Hack, Hy, Io, Julia, Kotlin, Lua, Mercury, Nim, OCaml, Raku, Rust, Scala, and TypeScript.
Welcome to Day 1 of this year’s A Language a Day Advent Calendar. Today’s topic is introduction to the TypeScript programming language.
Facts about the language
Some facts about TypeScript:
- TypeScript is a superset on top of JavaScript
- The source code compiles to plain JavaScript
- There is static type checking
- Using types is optional
- Appeared in 2012
- Developed by Microsoft
- Website: www.typescriptlang.org
Installing and running TypeScript
If you have node.js runtime installed on your computer, installing TypeScript is a single command:
$ npm install -g typescript
To compile the program, run its compiler:
$ tsc program.ts
This command generates a JavaScript file program.js that you can execute either directly from the command line, or via a web browser as you usually do with the JavaScript code. Here is how you run it via node.js in terminal:
$ node program.js
Hello, World!
Here is the minimal program that prints a phrase to the console.
console.log('Hello, World!');
Variables
You create variables by using one of the keywords: let
or const
. Actually, var
is also available, but let it stay in JavaScript. So, let
creates a variable, while const
— a constant.
let question = 'What is... ?'; const answer = 42;
You can specify the type of your variable by annotating it:
let question2: string = 'What is... ?'; const answer2: number = 42;
(Redefining the same name is an error in TypeScript, unlike JavaScript.)
Functions
A function can look like a traditional JavaScript function created with the function
keyword:
function personal_greeting(name) { return "Hello, " + name + "!"; } const my_name = "John"; console.log(personal_greeting(my_name));
Although it is wiser to add type annotations to specify the type of both parameters and the return value. You can omit the return type if it is obvious that it can be deduced by the compiler (it can be omitted in the following example).
function personal_greeting(name: string): string { // type annotation return "Hello, " + name + "!"; } const my_name = "John"; console.log(personal_greeting(my_name));
Optional and defaults parameters
Function parameters can be optional or you can give a default value to them. Optional parameters are marked with the question mark after the name:
function personal_greeting(name?: string): string { if (name) return "Hello, " + name + "!"; else return "Hi to everyone!"; } const my_name = "John"; console.log(personal_greeting()); // impersonal console.log(personal_greeting(my_name)); // personal
You introduce a default value after the equals sign:
function power(x: number, p: number = 2): number { return x ** p; } console.log(power(5)); // 25 console.log(power(5, 3)); // 625
A Factorial example
We are ready to create a function that computes a factorial. Let us make it recursive at the moment. If you know how to make an if
test in JavaScript, you are ready to code.
function factorial(n: number): number { if (n < 2) return 1; else return n * factorial(n - 1); } console.log(factorial(5)); // 120
Lambdas
Omit the function name and save its definition in a variable — and you’ve got a lambda function:
let sqr = function(x: number) {return x * x;}
console.log(sqr(5)); // 25
console.log(sqr(6)); // 36
You may want to use more modern syntax and use an arrow instead:
let sqr = (x: number) => x * x;
You can get even more and leave the type of the argument out, but you may get a hint from your IDE to put it back. In this case, the type of the x
parameter is any
.
let sqr = x => x * x;
Classes
In JavaScript, object orientation is based on prototypes. In TypeScript, you are additionally allowed to use more classical approach to build classes.
The following example demonstrates some of the straightforward features of TypeScript classes. Here, we define a class that has two data fields: a public property name
and a private member called age
. You need the private
keyword to declare private members. The use of the public
keyword is optional, as members are public by default.
class Person { name: string; private age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } info() { return this.name + " is " + this.age + " years old."; } }
Notice that you do not need any kind of let
or function
keyword to declare and define class properties and methods. Inside the class method, you refer to the current object via this
.
A member with a special name constructor
is obviously a constructor. It will be called when you create an object using new
. Correspondently, you pass the arguments:
let person = new Person("John", 30);
Now, you have a variable person
keeping an object of the Person
class. Its both data fields (name
and age
) are filled with the values you passed to the constructor.
You can access (and change) object’s public properties, but you can’t do that for private members. Use public methods to access the information:
console.log(person.name); // console.log(person.age); // illegal console.log(person.info());
Inheritance and Polymorphism
Let us create a zoo and populate it with at least two kind of animals. The goal here is to demonstrate how you work with inheritance in TypeScript and how it deals with polymorphic methods.
abstract class Animal {} class Cat extends Animal { who() {return 'Cat';} } class Dog extends Animal { who() {return 'Dog';} } let zoo = [new Cat(), new Dog()]; for (let animal of zoo) { console.log(animal.who()); // prints: Cat, Dog }
The two ‘concrete’ animal classes, Cat
and Dog
, are derived from their base class Animal
. To prevent creating pure animals, this class is made abstract
.
The zoo
array keeps two objects, and we loop over them using a for
…of
loop (notice, not for
…in
, which will iterate over indices rather than data). The animal.who()
call is working well for both objects and it calls the correct version of the who()
method depending of which object is currently kept in the loop variable animal
.
Interfaces
TypeScript also supports the concept of interfaces. Let us modify the previous solution with animals and use interfaces.
interface Animal { who(): string; } class Cat implements Animal { who(): string {return 'Cat';} } class Dog implements Animal { who(): string {return 'Dog';} } let zoo: Animal[] = [new Cat(), new Dog()]; for (let animal of zoo) { console.log(animal.who()); // prints: Cat, Dog }
Here, the Animal
interface demands the classes that implement it have the who()
method returning a string. Use the keyword implements
to say that the class uses the given interface. Also, notice how we made the zoo
array more strict. Now it has the type annotation Animal[]
that allows storing only Animal
objects there.
Error handling
TypeScript supports the try
–catch
block, where you can throw
an exception.
function divide($x: number, $y: number) { try { if ($y == 0) throw new Error('You cannot divide by zero'); else return $x / $y; } catch(e) { console.log('Error occured: ' + e); } } console.log(divide(42, 2)); // 21 console.log(divide(42, 0)); // undefined
The output of this program is the following:
21 Error occured: Error: You cannot divide by zero undefined
Note that division by zero by itself is not an error. You simply get an Infinity
.
Asynchronous computing
In TypeScript, there are promises, async and wait, which may be familiar to you if you follow the recent JavaScript standards. Instead of deep diving into it, let us examine it on the following example.
Sleep Sort
Let us create a program that implements the so-called Sleep Sort algorithm. This is a semi-joke approach to sorting small integer numbers. The numbers in the data sequence create a delay of the corresponding number of seconds, and then the number is printed.
While being not serious at a first glance, this algorithm is a good test for writing the code that employs the language features aimed at concurrency.
In the case of TypeScript, we can use the async
/await
keyword pair. The setTimeout
function is making the actual delay. Here is the complete code:
async function sort(n: number) { await new Promise(resolve => setTimeout(resolve, 100 * n)); console.log(n); } let data = [10, 4, 2, 6, 2, 7, 1, 3]; for (let x of data) sort(x);
You have to compile this program with the following library support (alternatively, use the tsconfig.json file):
$ tsc --lib ES2015,dom sleep-sort.ts
The program loops over the data and calls the sort
function for each of them. The function creates a promise and immediately returns. You can prove it by adding the print instruction in the loop:
for (let x of data) { sort(x); console.log("Submitted " + x); }
Each of the promises is resolved as soon as we have at least 100 * n
milliseconds passed. You can experiment with the multiplier to find the best balance between the robustness of the result and the speed of the program.
Get more
That’s all for today. Of course, a lot of TypeScript features are not covered in this article. I am sure you will like the way they do more type checking things, including generic functions and classes, async/await options, working with modules, and much more. I advise you to continue learning the subject using the following resources:
The source codes for today’s article about TypeScript you can find on GiHub.
Next: Day 2. Rust at a glance.