TypeScript at a Glance — A Language a Day, Advent Calendar Day 1/24

About this ‘A Language a Day’ Advent Calendar

Andrew Shitov. A Language a Day

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.

Learn more where to get the book

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 forof loop (notice, not forin, 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 trycatch 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Retype the CAPTCHA code from the image
Change the CAPTCHA codeSpeak the CAPTCHA code