A Type System

Kinanee Samson - Jul 14 '21 - - Dev Community

TypeScript in my opinion will always remain a superhero large scale application development tool, TypeScript comes loaded with a great type system and in this article i am going to write about arguably one of the biggest features of TypeScript which is the Type System.

But why Add Types?

This is one of the first questions i asked myself when i stumbled accross TypeScript, if valid JavaScript is TypeScript why bother adding extra codes to my already existing code base, now i see the importance of using TypeScript and i don't see myself working on a large scale application and using JavaScript, that's not just possible.

A Type system is there because it gives meaning to our code. A Type System coerces some form of sense and orderliness to our code. In JavaScript we can pass invalid types as arguments to a function. We can pass less number of arguments to the function or even more arguments than is required and all that will be fine until runtime. But you are working for an institution that is paying you hard earned dollars and time is money. How bout we catch this types of bugs before runtime? Enter a Type system.

To read more articles like this please visit Netcreed

A Type system goal is to provide a type definition for each variable, function, class, object. The types defined for each is used at compile time to perform some checks to ensure that each value assigned to a variable is of the type annotated with variable it is assigned to and if not, to expose the errors relating to wrong type of value that is passed to a variable.

TypeScript is statically typed because unlike JavaScript that performs this checks during runtime, the TypeScript compiler will actually perform this check when we try to compile our TypeScript code to JavaScript even as we are writing our code the TypeScript compiler is actually doing it's work and will notify us when we try to do something that's not valid.

Primitive Types And Functions

TypeScript provides types for all primitive values in JavaScript as we have seen from earlier articles. Functions can also be typed but instead they are called signatures.
A function signature specifies the number and type of arguments the function can accept. It also specifies the return type of the function.

// STRING
let name: string,
name='sam' // OR name = "sam"
// NUMBER
let age: number
age = 201 //
// BOOLEAN
let isOnline: boolean

// function signature
let sayHello: (person: string) => string
Enter fullscreen mode Exit fullscreen mode

sayHello can only accept a string and must return a string otherwise there will be a compile error.
Arrays are central to working with JavaScript and thus TypeScript also allows for type annotations with arrays.

// STRING ARRAY
let names: string[] = ['becker', 'ahmed', 'james']
names = [1, false, 17] // Not Okay
// NUMBER ARRAY
let prices: number[] = [1, 11, 7]
prices = ['shoes'] // Not Okay
Enter fullscreen mode Exit fullscreen mode

To gain more control over what element occupies a particular index in an array we TypeScript provides tuples. A tuple is a kind of array where each index of the array can only store a particular type of value.

// TUPLES
let arr :[number, string, boolean]
arr = [1, 'becker', true] // Okay
arr = [false, 'becker', 1] // Not Okay
Enter fullscreen mode Exit fullscreen mode

The array above can only store a number in it's first index, a string in it's second index and a boolean in the third index. Tuples are quite good when using the rest operator.

We can use interfaces to define the structure of an object or the shape of a class, or to combine multiple type definitions into a single type, an example of an interface is presented below;

interface Car {
    wheels: number,
    color: string,
    plateNumber: string,
    manufacturer: string,
    model: string
}
// Okay satisfies the contract
let lambo: Car = {
    wheels: 4,
    color: 'red',
    plateNumber: '234RE2',
    manufacturer: 'Lamborghini',
    model: 'sesto elemento'
}
// Not okay must satisfy the contract
let randCar : Car = {
    wheels: '2',
    plateNo: 23424,
}
Enter fullscreen mode Exit fullscreen mode

Union | Custom Types

Typescript also provide type alias for creating custom types and union types. Union types are for annotating variables that can store more than one type of value. While custom types allow us to create our own types from a primitive type or another type we created. We can also use literal values for type definition. When we do that any variable whose type or function whose signature accepts or returns that type will all deal with the literal value.

// TYPE ALIAS
type color: = 'red'
// COMBINING WITH UNION TYPES
type carColor = 'red' | 'green' | 'blue' | 'yellow'
// UNION TYPES
let plateNumber: string | number

let lamboColor:carColor = 'red' // Okay
lamboColor = 'purple' // Not Okay

Enter fullscreen mode Exit fullscreen mode

TypeScript's Type System

TypeScript type system originated from the type theory developed by Bertrand Russell who developed the theory in the early 20th century. The type theory is a system where each term is given a type and operations are restricted based on the types, if we pull up a comparison between TypeScript's type annotation and the type theory we will find a great detail of striking similarity.

// TYPE THEORY
z: nat

clickZ: nat -> nat
Enter fullscreen mode Exit fullscreen mode

This is a basic example of type theory building blocks, let's take a look at type annotations in TypeScript.

//TYPESCRIPT'S TYPE ANNOTATION
let num: number

let logNum: (num: number) => number;
Enter fullscreen mode Exit fullscreen mode

You see the similarity i spoke about earlier? Let's go on to discuss some attributes of TypeScripts type system.

Optional Static Typing

TypeScript is the product of the lessons learned from working with strongly typed languages like java and C#. So TypeScript comes with the benefit of optional typing. Plus TypeScript is a superset of JavaScript, we all know that JavaScript is dynamically types. Although this is not too good, but it comes with some benefits. Rather than being in a spaghetti like situation where you feel like you are typing your self to death. You can tell the TypeScript compiler to come of easy with the types because you don't know the actual type the variable will hold untill you assign a value to it. This can be a huge breather and gives you a sense of freedom and being in control.

// When we know the type of a value
let name: string = 'supes'
// When we don't know the type of value a hero will hold
let hero: any

hero = 'superman'
// OR
hero =  {name}
// OR
hero = true
// OR 
hero = 3
Enter fullscreen mode Exit fullscreen mode

Incase you feel confused about the shape of your object or the type of value it should store, just annotate it with any and you can work much like you do in JavaScript.

Type Inference

Another cool feature of the Type system employed by TypeScript is that if you don't specify the type for a variable, TypeScript will automatically infer the type of the value you pass to variable to it. And it leans towards making our code short and more clean especially if you assigning a value to a variable immediately after it is created. You don't actually need to annotate the variable with the type because that is really redundant.

//INSTEAD OF
let name: string = 'supes'
//RATHER USE
let job = 'coding'
let age = 20
// TypeScript will auto infer the string type to job
// and number to age
job = 600 // Not okay
age = false // Not okay
Enter fullscreen mode Exit fullscreen mode

If you plan to write code that is like above where you do things the JavaScript way, remember to annotate the variable with the any type.

Structural Typing

Unlike the early strongly typed language that uses a nominal typing system, TypeScript uses a structural typing system. But wait what is a structural typing system and what is a nominal typing system? In nominal typing system a variable is only of a valid type when we explicitly decorate the variable definition with that type.

Let's take a use case, we know that admin on a platform must be a user. In a nominal typing system an admin is not a user and only an admin. We have to explicitly decorate it with the interface for an admin for it to be valid. This kind of system prevents situation where an object with similar properties of an admin can be valid just because it looks like it. This is cool but i don't like this approach personally. And that's where structural Typing comes in to play.

Structural Typing system is actually concerned with the internal structure of an object, that is to say as far as an admin and a user has the same structure, a user is as valid as an admin. This kind of effect with structural typing is actually desired in TypeScript. We can also achieve the same result that a nominal typing system gives us with TypeScript. Let see TypeScript's structural typing system in play

type user = {
    name: string,
    id: string
}

let sayHello : (obj: user) => string
let sam: user = {
    name: 'sam',
    id: '1'
}
let superAdmin = {
    name: 'super',
    id: '11'
}
sayHello = obj:user => return `${obj.name} says hello`;
// VALID
console.log(sayHello(sam)) // sam says hello
// VALID
console.log(sayHello(superAdmin)) // super says hello
Enter fullscreen mode Exit fullscreen mode

If we wanted to achieve the nominal typing effect we can make use of generics, let's see a typical implementation

type userId = 'user'
type adminId = 'admin'

type user<uid extends string> = {
    name: string,
    id: uid
}

let sayHello: (obj: user<userId>) => string

let sam:user<userId> = {
    name: 'sam',
    id: 'user'
}

let superAdmin = {
    name: 'super',
    id: 'admin'
}
// POSSIBLE
console.log(sayHello(sam)) // sam
// NOT POSSIBLE
conosle.log(sayHello(superAdmin)) 
// Will show error in IDE
Enter fullscreen mode Exit fullscreen mode

Type Checking

One thing TypeScript does that makes our work much easier is type checking. Once we have defined the types for our variables TypeScript automatically go through each assignment in our code to ensure that for each variable defined the right type of value is assigned to it. For each function the right type of arguments are called with the function. It will also ensure that the function receives the right number of arguments.

let callPerson: (phoneNo: number) => string

callPerson = (phoneNo) => `calling ${phoneNo}...`

let callKala = callPerson(234804568890); // Okay

let callFrank = callPerson('234804568890') // Not Okay

callKala = 23 // Not Okay coz callKala is a string, type inference
Enter fullscreen mode Exit fullscreen mode

As we work with more complex objects and type definitions, TypeScript will test each property on each object. It will even check that each class has the right type of access modifiers for properties and in turn that they are expecting the same type and that they actually receive the right type of value. If the Object contains nested within it another object the same level of type checking will be performed on the object.

Widened Types

A widened type is a typical situation of a function call that returns null or undefined. An expression that returns either of the two also fits into

this category. And an assignment whose type is null.

let log = () => null

let widened = log()
Enter fullscreen mode Exit fullscreen mode

Type Erasure

When we compile our TypeScript code to JavaScript, the compiler will erase all type definitions, function signatures and interfaces from the compiled JavaScript code. This is because JavaScript as we know it does not support types.

That's it, hope you enjoyed it and found it useful. Personally my experience working with typescript has been superb, stay tuned for articles on TypeScript.

To read more articles like this please visit Netcreed

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .