From JavaScript to TypeScript Pt. I: Types & Variables

Get started with TypeScript and learn about types and function annotations.

Object-oriented programming (OOP) has been one of the most influential paradigms in the development of modern programming practices. If you've ever thought in terms of classes or inheritance, you prove the point: Those ideas have their origins in classical OOP.

TypeScript is a language that adds object-oriented syntactical sugar to JavaScript

Object-oriented programming has been so influential that many can't imagine doing things any other way. TypeScript's very existence is testament to this fact: It provides classically object-oriented syntax for a language that supports almost no classical object-oriented features.

Make no mistake: TypeScript does not add true classes, interfaces, or generics. Just some sugar to make it easier if you think that way. Regardless, it's a good introduction to the concepts of OOP for people whose background is mostly in JavaScript.

This series has two goals:

  1. Cover the essentials of TypeScript, for JavaScripters with an OOP background; and
  2. Cover the essentials of OOP, in the context of TypeScript, for JavaScripters with no formal exposure to it.

Those in the first group should find it easy to map the concepts we discuss to the syntax we'll see. Those in the latter group should find it easy to focus on the concepts without getting distracted by substantially different syntax.

We'll ease into it, and start in familiar territory. After reading this article, you'll be able to:

  1. Define type, as it relates to the JavaScript environment;
  2. List TypeScript's built-in types, and identify where they differ from JavaScript's;
  3. Declare variables with the proper types and access modifiers; and
  4. Annotate functions with the proper argument and return types.

Setting Up

To get started, you'll need to install TypeScript (TS). In this article, we'll be using Node.js, v5.11.0, and NPM, v3.8.6. You can also get TS bundled with Visual Studio 2015, if you prefer.

To install TypeScript, run:

npm install --global typescript

This installs a command-line tool, tsc, which you use to transpile TS to JavaScript.

Create a new directory somewhere: We'll save and compile our TS examples here.

# This creates a new directory in your home folder.
# Feel free to do this somewhere else.
cd && mkdir ts_examples

While simple, I've collected all of the examples in a GitHub repo. If you don't want to type, run:

git clone https://github.com/Peleke/oop_in_typescript
git checkout Part_1-Types

. . . And you're good to go.

Types

The word type means different things in different contexts. In general, types are a way of classifying data structures based on the kind of information they can contain, and what you can do with it.

Types in JavaScript

In JavaScript, clause 6 of the ECMAScript specification defines the following types:

  • Undefined;
  • Null;
  • Boolean;
  • String;
  • Symbol;
  • Number; and
  • Object.

To say a variable is of a certain type is to say two things:

  1. That it supports all the operations of that type; and
  2. That its internal representation is distinct from that of the other types.

At the level of applications programming, we're only concerned with the first point.

"use strict";

const name     = "Peleke";      // name has type string
const nothing = null;              // nothing has type null -- kind of*
const nemo     = undefined;   // nemo has type undefined
const obj          = { };                // obj has type object

// * Brendan Eich made a mistake in the 10-day whirlwind of putting together JavaScript.
//      typeof null returns "object", but it's not an object, and this is a bug.
//      Under the hood, null /is/ its own primitive type -- Null. You just can't tell.
//      For more: http://www.2ality.com/2013/10/typeof-null.html

Of course, if you've spent more than a few minutes writing JavaScript, you'll know that this is mostly trivia. We don't annotate types in JavaScript, and we can assign values of different types to the same variables through the lifetime of our program.

As far as we're concerned, if an object has the properties and methods we need it to, we don't care what the underlying type is. If it behaves properly, it's good enough. The rest is an implementation detail.

This is sometimes called duck-typing: If it walks like a duck, and it quacks like a duck, it's probably okay if we eat it treat it like a duck.

The Purpose of Types

Languages like Java and Haskell are statically typed. That means that, before they run your program, they'll check to make sure that you get strings where you expect strings, numbers where you expect numbers, and mongeese where you expect mongeese. If you pass a mongoose to a function that expects a string, it'll complain, and you'll have to fix it before your program will compile.

The point of all that trouble is to let you know when you've written code that might break before you run it. After all, if you write a function that expects a string, and you give it a mongoose, chances are good it's not going to know what to do with it.


private boolean screamName(String name) {
    return name.toUpperCase();
}

// Mongeese don't like to be capitalized.
//   Fortunately, the compiler prevents this from building.
tameMongoose(new Mongoose()); 

In JavaScript, we'd just go ahead and pass the mongoose along and cross our fingers. If it's got a toUpperCase, it works. If not, our program dies. Devil's gambit.

"use strict";

const sweet_mongoose = { };
const savage_mongoose = {};

sweet_mongoose.name = "sweetheart";
savage_mongoose.name = "savage";

sweet_mongoose.toUpperCase = function () { return this.name.toUpperCase(); }

function screamName (name) {
  return name.toUpperCase();
}

screamName(sweet_mongoose); // What a SWEETHEART.
screamName(savage_mongoose); // Kills program with a TypeError. Cold-blooded.

The idea behind all this is that a value's type can be conceived of as a sort of interface. In other words, a object's type reveals what you can and cannot do with it. Attempting to do something with an object that doesn't make sense for its type -- like capitalizing a number -- is a programmer error that a type-aware compiler can catch ahead of time.

Therein lies one of the benefits: We trade runtime errors for compile-time warnings. By contrast, dynamic languages eschew compile-time safety checks in favor of greater flexibility and programmer productivity.

Obviously, neither system is perfect:

  1. In our Java function, what if the mongoose did have a toUpperCase method that behaved as desired? Then we'd have to do some finaggling just to get our program to compile.
  2. In Javascript, what if we get an object with the function we expect, but different behavior? An Observable and an RSS feed have very different subscribe methods. We'll obviously notice an error, but there's a substantial chance the program would execute -- just not correctly.

That said:

  1. Types are a form of self-documenting code.
  2. Certain type errors are easy to catch at compile time, but nonobvious at runtime.
  3. Types help with the design of function compositions and complex reactive streams.

Do we need static typing? Obviously not. JavaScript doesn't have it, and the Internet works fine.

Do we benefit from it? Maybe. It's powerful in languages like Java. It's exceedingly powerful in certain statically-typed functional languages, like ML, Haskell, and Scala.

TypeScript takes the stance that we unequivocally do benefit from static typing, allowing you to declare your variables with any of a handful of built-in types.

TypeScript's Basic Types

TypeScript exposes nine types for us to work with. Whether you use them or not is up to you: Type annotations are always optional in TypeScript.

  1. Boolean;
  2. Number;
  3. String;
  4. Array;
  5. Tuple;
  6. Enum;
  7. Any;
  8. Void;
  9. Function.

We'll start with the first eight, and circle back to the last one a bit later.

Boolean

In TypeScript, you declare variables as in JavaScript, with a colon and type after the variable name. Booleans behave as usual.

Type Keyword : boolean

"use strict";

const lie : boolean = false,
       truth : boolean = true;

Save that in a file called boolean.ts, then run tsc boolean.ts. It'll create a file called boolean.js, Open it up to see some sweet, sweet vanilla JavaScript:

"use strict";
var lie = false, truth = true;

Number

TypeScript's Number type is synonymous with JavaScript's Number type. If youre curious, JavaScript numbers are an IEEE 64-bit binary format double precision floating point values. Or, you know, just numbers.

Type Keyword : number

"use strict";

const pi : number = 3.14159;

String

As with Number, TypeScript's String is synonymous with JavaScript's underlying String type.

Type Keyword : string

"use strict";

const tree_of_knowledge : string = "Yggdrasil";

Array

TypeScript treats arrays as their own type, and requires that you declare the type of what's inside of them, as well.

Type Keywords

  1. Array<[ Item Type]>
  2. []

There are two syntaxes for arrays.

"use strict";

// The item type, T, followed by brackets means, "an array whose items are of type T."
const divine_lovers : string[] = ["Zeus", "Aphrodite"];

// Writing Array<[Item Type]> means the same thing.
const digits : Array<number> = [143219876, 112347890];

// But this doesn't work. Hm. . . 
const only_strings : string[] = [];
only_strings.push("This Works!")
only_strings.push(42); // This doesn't.

Well. That's annoying.

We can't put 42 into only_strings because we declared that it can only contain items of type string. This is an example of TypeScript's compile-time complaints in action.

We'll see how to fix this shortly. But first -- tuples!

Tuples

Tuples are another collection data type. They're useful for collecting a known number of items into an array-like structure. You can declare each item to be of a specific type.

Type Keyword : [(Item Type), . . . , (Item Type)]

"use strict";

// [Date, Month, Year] :: Triplet of numbers
let date_triplet : [number, number, number];
date_triplet = [31, 6, 2016];

// [Name, Age]
let athena : [string, number];
athena = ['Athena', 9386];

// You retrieve items with indexes, as with arrays. 
//  TS remembers the type for you.
var name : string = athena[0];
const age : number = athena[1];

// But:
name  = athena[1]; // No dice. We're writing TypeScript now.

So . . . What are tuples useful for?

Good question. JavaScript is the only language I write without native tuples, but I rarely use them elsewhere. Occasionally, I use them to group related data that isn't liable to change:

"use strict";

//The Big Three of Powerlifting
let big_three : [string, string, string];
big_three = ['Squat', 'Deadlift', 'Bench Press']

. . . But in general, I use a Map or Set instead.

Enum

Enums allow you to associate names with intever values. The name comes from the fact that they're enumerable.

Creating a enum effectively creates a new type keyword, which you use like any other type.

"use strict";

// This is an example from the TypeScript docs.
//    http://bit.ly/1XQjl2Y
enum Color { Red, Green, Blue };
const red : Color = Color.Red;

// Enums are like associative arrays. Each enum constant is associated with an index, starting at 0.
console.log(Color[0]); // 'Red'

// You can start from any number instead of zero, if you want.
//    Note that indexing a non-existent enum constant returns undefined,
//    but doesn't throw an error.
enum RomanceLanguages { Spanish = 1, French, Italian, Romanian, Portuguese };
console.log(RomanceLanguages[4]); // 'Romanian'
console.log(RomanceLanguages[0]); // undefined 

Symbols provide an interesting alternative to enums.

Any

Any is more or less what it sounds like: A type that accepts any value. In some sense, it's a way to opt out of type checking. It also allows us to declare mixed collections.

Type Keyword : any

"use strict";

let mystery : any = 4; // number
mystery = "four"; // string -- no error

const not_only_strings : any[] = [];
not_only_strings.push("This Works!")
not_only_strings.push(42); // This does too.

Void

Finally, we have Void. This is the typeclass associated with the values undefined or null. Unlike the other types, you won't use this to declare variables.

It's not that you can't. Just that, if you do, you can only set that varible to null or undefined.

"use strict";

let the_void : void = undefined;
the_void = null;

the_void = "nothing"; // Error.

The main reason void exists is to mark functions without return statements.

Speaking of which, it's about time we talk about functions.

Return Types

TypeScript allows you to mark the type of the value that a function returns.

Just as a variable's type indicates what sort of data the variable contains, a function's return type indicates what sort of data it returns.

All the types we've seen so far are valid return types. In particuar, you'll use void almost exclusively as the return type of a function with no return statement.

You denote a return type the same way you denote a variable type: With a colon and a type keyword, this time after the arguments list.

"use strict";

function capitalizeName (name : string) : string {
  return name.toUpperCase();
}

// Shoutout to @achm4m0 (Alberto) for notifying me of an error here!
console.log(capitalizeName('geronimo')); // 'GERONIMO'

// But:
console.log(capitalizeName(42)); // Error; 42 isn't a string.

// TypeScript checks at compile-time if you're returning the right type.
function even_broken (num : number) : boolean {
   return (num % 2); // WRONG. This will cause a compile-time error.
}

function even (num : number) : boolean {
   return (num % 2 == 0); // Much better; this works.
}

Note that you can also mark argument types as you would that of normal variables.

Notifications like the one we get for even_broken are pretty helpful during development.

The Last Type: Functions

One of JavaScript's greatest strengths is that it treats functions as first class citizens. This means we can pass them as arguments and return them from functions ike any other value. How does TypeScript handle that?

Simple. Function name; parenthetical list of typed arguments; fat arrow; return type.

"use strict";

let multiply : (first : number, second : number) => number;
multiply = function (first, second) {
  return first * second;
}

This example illustrates that you can annotate your functions types before defining them.

The same pattern applies if you're returning a function. In that case, though, you wrap the type of the function you return -- that is, everything after the fat arrow -- in parentheses.

"use strict";

let multiplyFirst : ( first : number) => ((second : number) => number);
multiplyFirst = function (first) {
  return function (num) {
    return first * num;
  }
} 

console.log(multiply(5, 2)); // '10'
console.log(multiplyFirst(9)(2)); // '18'

Newcomers to static typing often find variable types annoying. Function types are where they start to get nauseated.

This is actually the typing feature I use most in TypeScript. Somewhat ironically, that's because of my background in functional programming, not OOP, and that remains the context in which I find it most useful.

Annotating functions that return string or number is easy, and beneficial largely for the purpose of self-documentation.

On the other hand, writing function type annotations is an integral, active part of my design process when I work with Rx streams and complex [function compositions](https://en.wikipedia.org/wiki/Function_composition_(computer_science).

Conclusion

By now, you should feel like you've started getting your hands dirty with TypeScript. You've seen:

  1. How to transpile TS files on the command line;
  2. How types in TS compare to JavaScript's;
  3. All of the types TS has to offer; and
  4. How to annotate function types.

You should also feel pretty confident that you can article why we might benefit from all this in the first place.

Next time, we'll stray a little farther from familiar territory, and explore classes; interfaces; enums; and how to get the most out of them by adapting classical obect-oriented design to our more dynamic environment.

Until then, feel free to drop questions in the comments below, or shoot them to me on Twitter (@PelekeS).

Peleke Sengstacke

Peleke Sengstacke is a web and Android developer with a soft spot for functional programming.

He likes linguistics, Haskell, and powerlifting. He dislikes mosquitoes, cramped bus rides , and merge conflicts.

Catch him on Twitter (@PelekeS), or sign up for his email list on what to learn and where to learn it (http://www.tinyletter.com/PelekeS). It's wicked educational.