Joel Ross
Winter 2017

TypeScript

Structure of the Day

  • Admin & Readings

  • TypeScript Basics

  • Variable Scoping

  • Arrow Functions

  • TypeScript & Modules

https://www.flickr.com/photos/littlehaulic/

Reading Review

Questions or thoughts on the reading?
 

Items of note:

  • Software architectures evolve over time
    (Ch 1.2 - 1.3)

  • The whats, whys, and whens of software architecture
    (Ch 2.1 - 2.3)

Loose (Weak) Typing

JavaScript variables are dynamically typed, so they take on the type (Number, String, Array, etc) of their current value.

/* JavaScript */

var x = 42; //value is a Number

x = 'forty-two'; //value is now a String

function sayHello(name) {
  console.log("Hello, " + name);
}

//can pass any variable type we want
sayHello( "Ada" );
sayHello( [1,2,3] );

Strong Typing

Java variables are statically typed, so their type is declared ahead of time.

/* Java */

int x = 42; //value is a Number

x = "forty-two"; //Compile error: incompatible types

What are the benefits of this?

Static Analysis

Strongly typed languages allow for additional static testing: testing a program without actually running it!

Static analysis tools (i.e., linters) analyze the source code for errors through complex pattern matching.


This allows you to catch errors at compile-time (easier to fix!) rather than at run-time.

Can have some interesting (social) limitations: see here

TypeScript

A typed superset of JavaScript that compiles to plain JavaScript.

  • Developed and maintained by Microsoft
  • First released in Oct 2012; v2.0 released in Sep 2016

Install TypeScript

npm install -g typescript

install globally so can
run from command-line

Install via npm

TypeScript Code

Write TypeScript code in .ts files

//index.ts
function sayHello(name) {
  console.log("Hello, "+name);
}

sayHello("y'all");

Can be normal
JavaScript!

Use tsc to compile code into normal .js files

## In Terminal

# produces index.js (in same folder)
tsc index.ts   

# run normally
node index.js  
## In Terminal

# produces index.js (in same folder)
tsc index.ts   

# run normally
node index.js

# watch for changes
tsc --watch index.ts

auto-recompile on file save

TypeScript Code

Can include TypeScript specific syntax. Compiling will report any type-based errors! (Also seen in VS Code)

//index.ts


function sayHello(name: string) {
  console.log("Hello, "+name);
}

sayHello("y'all");

Type Annotation:
         parameter must be a String

//index.ts


function sayHello(name: string) {
  console.log("Hello, "+name);
}

sayHello("y'all");
sayHello(1); //compile error!

Compiler Options

There are many compiler options you can give to tsc, but it's easiest to define a tsconfig.json file to save the options:

{
  "compilerOptions": {
     "target": "es5",         //compile to ES5 code
     "module": "commonjs",    //use commonjs style modules
     "sourceMap": true,       //include sourcemaps (for debugging)
     "outDir": "build/",      //where to save .js files
     "alwaysStrict": true,    //auto add "use strict" (2.1+)
     "removeComments": true,  //remove comments!
   },
   "include": [
     "src/**/*"               //which .ts files to compile (src/)
   ],                         //leave off for "all in folder"
   "exclude": [
     "node_modules",          //files NOT to compile
   ]
}
# run `tsc` with no arguments to use the tsconfig options
tsc

Type Annotations

We add type information to variables by putting a colon : and the type after the variable name:

// a variable that is a Number
var age: number = 23

// a variable that is a String
var firstName: string = "Ada"

// a variable that is a Boolean
var isTired : boolean = true;

The compiler will report an error if we violate the type.

//cannot change type
age = "old"; 

//illegal operation
firstName = "Beetlejuice"*3;

//no such property
console.log( isTired.length ); 

Will still compile to .js,
just reports errors!

white space ignored

Type Inference

Typing is inferred (automatically determined) even if we don't explicitly type variables! 

// a variable that is a Number (inferred)
var age = 23

age = "old"; //error, type mismatch

TypeScript uses a "type matching algorithm", but it can be wrong: use explicit types when needed!

Arrays

A list of ordered elements like in Java... mostly

var letters = ['a', 'b', 'c'];
var numbers = [1, 2, 3];
var things = ['raindrops', 2.5, true, [5, 9, 8]];
var empty = [];
var blank5 = new Array(5); //empty array with 5 items

//access using [] notation like Java
console.log( letters[1] ); //=> "b"
console.log( things[3][2] ); //=> 8

//assign using [] notation like Java
letters[0] = 'z';
console.log( letters ); //=> ['z', 'b', 'c']

//assigning out of bounds automatically grows the array
letters[10] = 'g';
console.log( letters); 
    //=> [ 'z', 'b', 'c', , , , , , , , 'g' ]
console.log( letters.length ); //=> 11

Array Types

We can declare arrays as containing elements of (only) a particular type by annotating them with the element type followed by square brackets []

//an array of strings (a `string[]`)
var letters: string[] = ['a', 'b', 'c'];

//an array of numbers (a `number[]`)
var luggageCombo: number[] = [1, 2, 3, 4, 5];

//arrays can only have a single type
letters.push(12); //Error!

Tuples

We can also define tuples, which are arrays with multiple types of elements in a particular order:

//declare variable with a tuple type
var nameAge: [string, number];

//initialize it
nameAge = ["Ada", 23]; // OK

//all in one line
var nameAge2: [string, number] = ["Bob", 34];


//tuples are ordered!
nameAge = [23, "Ada"]; // Error!

Objects

An unordered set of " key & value" pairs.

  • Like a dictionary: have the word (the key) and the definition (the value). Use the key to "look up" the value.

  • a.k.a a Map or a Hash (or HashMap in Java)

  • a.k.a. an associative array

ages = {ada:23, bob:34, charles:13}
extensions = {'joel':1622, 'ischool':9937}
num_words = {1:'one', 2:'two', 3:'three'}
things = {num:12, dog:'woof', list:[1,2,3]}
empty = {}
empty = new Object(); //empty object

//access values
console.log( ages['ada'] ); //=> 23
console.log( ages['fred']); //=> undefined

//assign values
ages['alice'] = 41;
console.log( ages['alice'] ); //=> 41

ages['fred'] = 19; //adds the key and assigns a value to it

Object Types

We can declare object types somewhat like tuples, by giving a "template" with the keys and their types:

//declare an object with a specific structure
var student: {first:string, last:string, age:number};

//initialize it
student = {
   first: 'Ada',
   last: 'Wong',
   age: 23
}

//all in one line!
var student: {first:string, last:string, age:number} = {first:'Bob', last:'Smith', age:34};

It is cleaner to do this with an interface , which we will talk about next week

Functions

Functions in JavaScript are like static methods in Java

//plain JavaScript
function greet(name){ //named function
    return "Hello, "+name;
}

var msg = greet("Joel"); //call and assign result
//named function
function sayHello(person){ 
   console.log("Hello, "+person); 
}

//anonymous function (no name!)
function(person) {
   console.log("Hello, "+person);
}

//anonymous function (value) assigned to variable
var sayHello = function(person) {
   console.log("Hello, "+person);
}

Function Types

When declaring functions, we use the same colon : syntax to denote the types of the parameters and the return type

function greet(name:string, loud:boolean) : string {
   if(loud) {
     return ("Hello, "+name).toUpperCase();
   }
   return "Hello, "+name;
}



//anonymous function assigned to variable
var hail = function(name:string, loud:boolean):string {
  //...   
}

parameters include types
like other variables

return type after param list, before block

param types

return type

Function Parameters

In TypeScript all parameters are required.

function fullName(firstName: string, lastName: string) {
   return firstName + " " + lastName;
}

//Error, too few parameters!
var name1:string = fullName("Bob");            

//Error, too many parameters!
var name2 = buildName("Bob", "Adams", "Sr.");

//works!
var name3 = buildName("Bob", "Adams");

return type is inferred!

Optional Parameters

We can make a parameter optional by adding a ? to it. Optional parameters must be the last one(s).

function sayHello(name?:string) : void {
   if(name) {
     console.log("Hi "+name+"!");
   }
   else {
     console.log("Hello");
   }
}

sayHello();        //=> Hello
sayHello("world"); //=> Hi world!

does not return anything!

Default Parameters

We can give a parameter a default value as well by assigning it a value in the parameter list.

function walk(animal = "dog") {
   console.log("Walk the "+animal);
}

walk();               //=> Walk the dog
walk("hippopotamus"); //=> Walk the hippopotamus

This feature is awesome

Class Types

Finally, just like in Java, we can define classes that introduce their own type for variables. MORE ON THIS NEXT WEEK!

class Square {
  private size: number;

  constructor(size: number) { this.size = size; } 
}

class Circle {
  private radius: number;

  constructor(radius: number) { this.radius = radius; }

  getDiameter(): number {
    return Math.PI*this.radius;
  }
}

var shape:Circle = new Circle(5);
shape.getDiameter();

ES 6 Features

TypeScript includes many "modern" JavaScript features, such as those introduced in ES 6. These features are compiled into "older" compatible JavaScript code.

ES 5 (2011) supported by most browsers

ES 6 (2015) mostly supported by some
                     browsers

ES 7 (June 2016) not reliably supported

Lexical Scope

Normally JavaScript variables are scoped lexically (where you write them at authoring time). That is, they are scoped to functions, not to control structures or other blocks!

/*scoping is ONLY to functions, not to blocks! */
function guess(num) {
  if(num == 12){
    var secretMessage = "You guessed my number!";
  }
  console.log(secretMessage); //access outside block
}

guess(12);
console.log(secretMessage); //ReferenceError

Scoping Quirks

Lexical scoping introduces a few quirks, particularly with asynchronous functions.

//What does this output?
for(var i=0; i<5; i++){
   setTimeout(function() { console.log(i); }, 500);
}

executes a function
after a delay

an anonymous
callback function
to execute

run function
after 500ms
(second param)

Each time through the loop it "queues" a function to run. But when that function is executed, it refers to the same i variable, which has increased to 5!

Closures

A closure is a coding pattern for associating lexically scoped variables with functions. It "encloses" a variable's value in a function.











for(var i=0; i<5; i++){
   //                      anonymous function
   setTimeout(function() { console.log(i); }, 500);
}










for(var i=0; i<5; i++){ 
   //name the anonymous function
   var myFunc = function() { console.log(i); };   

   setTimeout(myFunc, 500);
}
//define a function
function makeNumPrinter(num) {
  var localNum = num; //save the variable (function scope)

  //define a NEW function that references that variable
  var printFunc = function() { console.log(localNum) };

  return printFunc; //return that function!
}

for(var i=0; i<5; i++){ 
   //name the anonymous function
   var myFunc = function() { console.log(i); };   

   setTimeout(myFunc, 500);
}
//define a function
function makeNumPrinter(num) {
  var localNum = num; //save the variable (function scope)

  //define a NEW function that references that variable
  var printFunc = function() { console.log(localNum) };

  return printFunc; //return that function!
}

for(var i=0; i<5; i++){ 
   //create closure instead of anonymous function
   var myFunc = makeNumPrinter(i);   

   setTimeout(myFunc, 500);
}
//define a function
function makeNumPrinter(num) {
  var localNum = num; //save the variable (function scope)

  //define a NEW function that references that variable
  var printFunc = function() { console.log(localNum) };

  return printFunc; //return that function!
}

for(var i=0; i<5; i++){ 
   //create closure instead of anonymous function
   setTimeout(makeNumPrinter(i), 500);
}

the closure

Design Pattern

A description of a common problem and a solution to that problem.

Note: not "plug-and-play" solutions!

Block Scoping

ES 6 introduces two new keywords that can be used in place of var when declaring variables.

let provides block scoping

function guess(num) {
  if(num == 12){
    let secretMessage = "You guessed my number!";
  }
  console.log(secretMessage); //ReferenceError !!
}
function guess(num) {
  if(num == 12){
    var secretMessage = "You guessed my number!";
  }
  console.log(secretMessage); //logs message
}

const defines constants (block scoped)

const PI = 3.1415;
PI = 21/7; // TypeError: Assignment to constant variable

always use this!

Let and Loops

When used in a for loop, let creates a new lexical scope at each iteration. This makes things work normally (no need for closures here!)

//Works as intended
for(let i=0; i<5; i++){
   setTimeout(function() { console.log(i); }, 500);
}

Template Strings

You can declare multi-line Strings in backticks ( ``), and use ${} to inject variables into the String (without needing to concatenate!)

//provide a name, an animal, and a verb
function excuse(name:string, animal:string, verb:string) {
  let email = `Hello Professor ${name},

Please excuse my missing assignment, 
as my ${animal} ate it.

${verb} you later,
A Student`;

  console.log(email);
}

excuse('Joel', 'Lemur', 'Smell');

Arrow Functions

ES 6 provides a shortcut syntax for declaring functions using =>

//normal function declaration
let foo = function(params:any):string {
  return 'foo '+param;
}
//arrow function declaration (block body)
let foo = (params:any):string => {
  return 'foo '+params;
}
//arrow function declaration (concise body)
let foo = (params:any):string => 'foo '+params;

single expression implies return

//arrow function declaration (concise body)
let sayHi = (name:string) => console.log('Hi '+name);

anonymous function assigned to a variable

Arrow Functions

ES 6 provides a shortcut syntax for declaring functions using =>

//normal function declaration
let array:number[] = [1,2,3];
array.map(function(num) {
  return num*2; //multiply each item by 2
});
//arrow function declaration (block body)
let array:number[] = [1,2,3];
array.map(num => {
  return num*2; //multiply each item by 2
});
//arrow function declaration (concise body)
let array:number[] = [1,2,3];
array.map(num => num*2);

Arrow Functions

ES 6 provides a shortcut syntax for declaring functions using =>

//normal function declaration
let array = [1,2,3];
array.map(function() { //no params! (for reasons)
  return 12; //map everything to 12
});
//arrow function declaration (block body)
let array = [1,2,3];
array.map(() => {
  return 12; //map everything to 12
});
//assign function to a name
let make12 = () => 12;
//arrow function declaration (concise body)
//assign function to a name
let make12 = function() {
  return 12;
};

Syntactic Sugar causes cancer of the semicolon

TypeScript and Modules

Modules

One way to follow the Principle of Separation of Concerns in an architecture is to separate the implementation (code) into modules. Each module is responsible for a distinct set of functionality.

 

Modules provide a code-based view of an architecture. 

In TypeScript (as well as in Node and ES6), each file is treated as a separate module of code. We can then explicitly import (load) functionality from one module into another in order to connect them.

TypeScript Modules

Requiring Modules

We "import" modules in Node by using the built-in require() function. This gives us access to the other module within ours!

CommonJS (used by Node.js)

ES 6 Modules (used by Browsers and TypeScript)

var random = require('random');

node version of "import"

global to refer to the library

module library name

import random from 'random';

global to refer to the library

module library name

Export / Import

We can make our own usable modules (files representing separate units of code) by exporting values from one file and importing them into another.

/*** my-module.ts ***/

export let joke = "Why'd the chicken cross the road?"
/*** index.ts ***/

import { joke } from './my-module';


console.log(joke); // prints the joke

./ indicates file in same folder
File extension optional

name of specific variable in {}

Named Exports

/***my-module.ts***/
export function foo() { return 'foo'; } //make available

function bar() { return 'bar'; }
export bar; //export previously defined variable

export { bar as barFunction }; //provide consumer name

//will not be available (a "private" function)
function baz() { return 'baz'; }
/***index.ts***/
import {foo, barFunction} from './my-module'; //multiple vars
foo() //=> 'foo'
barFunction() //=> 'bar'

import {foo as myFoo} from './my-module'; //rename import
myFoo(); //=> 'foo'

import * as module from './my-module'; //import everything
module.bar(); //=> 'bar'
module.baz(); //Error [private function]

We can export multiple variables with specific names.

Default Exports

A module can export a single default variable, which provides a slight shortcut for importing the variable (and makes modules easier to consume).

/***my-module.ts***/
export default function bark() { return 'Woof!'; }
/***index.ts***/
import speak from './my-module';


speak(); //=> 'Woof!'

name to refer to default by

default exports can also be anonymous.

/***animal-module.ts***/
export default ["dog","cat","bird"];

Exports Object

The pattern used in Node's module system (CommonJS) is to export a single object containing all the relevant variables and functions. This is similar to but incompatible with default exports. Typescript does support this pattern:

/***utils.ts***/
export = { //export a single object
 favoriteNumber: 12, //property
 foo: function() { return 'foo'; }, //property
 square: (n) => n*n, //property (arrow function!)
}
/***index.ts***/
import utilObj = require('./utils');



utilObj.square(4); //=> 16

use `export =` syntax

use `import = require()` syntax

NOT Node's require() function!

Node Modules and TS

The import = require() syntax is how we would import Node modules that are not written in TypeScript (such as Node's built-in readline module for user input).

//***index.js***/
//use Node's require() function
var readline = require('readline'); 

var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.question('Where do you want to go?', function(answer) {
  console.log('You said "'+answer.toUpperCase()+'"!');

  rl.close(); //close the whole interface once 
              //completely done asking questions
});

Normal JavaScript version

//***index.ts***/
//use TypeScript's import = require() syntax
import readline = require('readline'); 

var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.question('Where do you want to go?', function(answer) {
  console.log('You said "'+answer.toUpperCase()+'"!');

  rl.close(); //close the whole interface once 
              //completely done asking questions
});

TypeScript version

Declaration Files

In order to have type annotations on non-TypeScript modules, we need to provide type declarations (that the TS compiler can use) for those modules.

These are usually stored in .d.ts files.

A repository of these can be managed by the Typings application, and they can also installed via npm:

# install type definitions for node
npm install --save @types/node

package to install types of

Using Package.json

We can use npm to automatically install all the libraries we depend on (listed in package.json).

Install dependencies in project

# change to current project directory
# e.g., cd lec02-typescript
cd path/to/project

# install dependencies (may take a few minutes)
npm install

important!

install all the things!

TypeScript & Libraries

We can install Type Definition files in order to be able to use external libraries in TypeScript just like with Node.

# install module (e.g., lodash)
npm install --save lodash

# install type definitions
npm install --save @types/lodash
/***index.ts***/
import * as _ from 'lodash';

_.times(5, console.log); //count

For libraries without type definitions, fall back to the Node require() function.

/***index.ts***/
const dogNames = require('dog-names'); //Node require()
dogNames.femaleRandom();

ACTION ITEMS!

For Tuesday

  • Do the reading and "quiz" for Tuesday!

  • Homework 0 due Sunday night

  • Check out Homework 1 once available (~Friday)


Tuesday Lecture: Object-Oriented Programming!

arch-w17-typescript

By Joel Ross

arch-w17-typescript

  • 1,657