Type the Script
by Łukasz Karpuć
Me
- 10 years of professional experience
- 6 years in Sabre
- 2 years of persuading people to use types(cript)
Agenda
- Taking the bull by the horns
- TypeScript basics
- Type systems
- The purpose
Type systems + TypeScript in general
Out of scope
- Deep diving into type systems
- Language tutorial
- Advanced types in TypeScript
Anything not starting on "T"
Taking the bull by the horns
Text
"to do something difficult in a brave way"
JavaScript
class Turtle {
give();
save(data);
}
class Tortoise {
export();
save(data);
}
let first = new Turtle();
let second = new Turtle();
let third = new Tortoise();
second.save(first.give());
first.save(second.give());
second.save(third.give());
// ERROR!
// Undefined is not a function!
DUCK-TYPED JS
class Turtle {
give();
save(data);
}
class Tortoise {
export();
save(data);
}
let first = new Turtle();
let second = new Turtle();
let third = new Tortoise();
if( typeof first.give === 'function' ) {
second.save(first.give());
}
if( typeof second.give === 'function' )
first.save(second.give());
}
if( typeof third.give === 'function' )
second.save(third.give());
}
// But second one did not get data..
DUCK-TYPED JS
class Turtle {
give();
save(data);
}
class Tortoise {
export();
save(data);
}
let first = new Turtle();
let second = new Turtle();
let third = new Tortoise();
if( typeof first.give === 'function' ) {
second.save(first.give());
} else {
second.save(first.export());
}
if( typeof second.give === 'function' )
first.save(second.give());
} else {
first.save(second.export());
}
if( typeof third.give === 'function' )
second.save(third.give());
} else {
second.save(third.export());
}
// But what if .export() returns
// different data than .give()?
TypeScript
interface Data {
// ...
}
class Turtle {
give(): Data;
save(data: Data);
}
class Tortoise {
export(): Data;
save(data: Data);
}
let first = new Turtle();
let second = new Turtle();
let third = new Tortoise();
second.save(first.give());
first.save(second.give());
second.save(third.export());
// No syntax errors :)
TypeScript basics
Simple types
let motto: string = "abc";
let motto2 = "abc";
let counter: number = 1;
let counter2: number = 1.5;
let flag: boolean = true;
// has sense if strict null checks turned on
let flag2?: boolean = undefined;
let numbers: number[] = [1, 2, 3];
STILL Simple types
let tuple: [number, string, boolean] = [1, "text", true];
let simpleMap: {[key: string]: number} = {
"one": 1,
"two": 2
}
let customArray: {[idx: number]: number} = [1, 2, 3];
let something: any = 1;
something = "aaa";
let nothing: never = 1; // ERROR
let emptyArray: never[] = [];
// TS 3.1+
let strangeThing: unknown = "aaa";
something.charAt(0); // WORKS
strangeThing.charAt(0); // TS ERROR
Functions
function foo(a: number, b: string = "aaa"): void {
console.log(a, b);
}
const bar: (a: number, b: string) => void =
(a: number, b: string = "aaa"): void => console.log(a, b);
OOP
enum E { A, B };
interface I {
foo(): void;
}
abstract class Base implements I {
private counter: number = 1;
protected barAttr: () => void;
constructor(private initial: E) {};
public abstract foo(): void;
}
class Impl extends Base {
public foo(): void {
// ...
}
}
Generics
interface X<T> {
foo(): T;
}
class Y implements X<string> {
foo(): string {
return "123";
}
}
/// functions also may use generics
function bar<T>(): T {
// ...
};
Type Aliases
type A = string;
type B = { [key: string]: A };
let a: A = "abc";
let b: B = { "abc": "abc" };
type C = A | B;
let c: C;
c = a;
c = b;
c = Math.random(); // Type 'number' is not assignable to type 'C'
type D = A & B;
let d: D;
d = a; // Type 'string' is not assignable to type 'B'
d = b; // Type 'B' is not assignable to type 'string'
Type SUBSETS
type StringSubset = 'a' | 'b' | 'c';
let foo: StringSubset;
foo = 'a'; // OK
foo = 'd'; // ERROR
type NumberSubset = 1 | 2 | 3;
let bar: NumberSubset;
bar = 1; // OK
bar = 0; // ERROR
bar = 1;
bar--;
bar === 0; // TRUE
// types works before run-time only
Before Run-time?
What does it mean?
Type systems
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
Static typing works before run-time.
Dynamic typing works in run-time.
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
// STATIC
let a: number; // type set here
a = 1;
// DYNAMIC
let a;
a = 1; // type set here
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
Strong typing makes type of variable immutable.
Weak typing allows type of variable to be mutable.
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
// STRONG
let a: number;
a = 1;
a = "abc"; // ERROR
// WEAK
let a;
a = 1; // type becomes number
a = "abc"; // OK, type becomes string
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
Nominal typing uses type name before run-time.
Structural typing uses type structure before run-time.
Duck-typing is a technique that checks object structure in run-time.
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
// NOMINAL
class A {};
class B {};
let a: A = new B(); // ERROR
// STRUCTURAL
class A {};
class B {};
let a: A = new B(); // OK...
// ...structures match
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
Manifest typing reads explicit type names from source code.
Type inference deduces types from code context.
Both works before run-time.
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
// MANIFEST
let a: string = "abc";
a = "def";
a = 1; // ERROR
// TYPE INFERENCE
let a = "abc"; // type == string
a = "def";
a = 1; // ERROR
// faster coding
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
// MANIFEST
let a: string;
a = "abc";
a = "def";
a = 1; // ERROR
// TYPE INFERENCE
let a; // type == any
a = "abc";
a = "def";
a = 1; // still OK, be careful
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
// MANIFEST
function(): number { return 1 };
// re-implemented into
function(): number { return "A" };
// ERROR - interfaces stay safe
// TYPE INFERENCE
function() { return 1 }; // ()=>number
// re-implemented into
function() { return "A" }; // ()=>string
// NO ERROR - interfaces broken
TYPE SYSTEMS
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
TypeScript's
TYPE SYSTEM...
- static vs dynamic
- strong vs weak
- nominal vs structural
- manifest vs type inference
THE PURPOSE
The PURPOSE
- Manifesting intentions
- Freeing mind
- Decomposing systems
- Helping IDE to help you
- Making tests easier
- Simplifying design patterns
Manifesting intentions
Manifesting intentions
function setId(id);
Manifesting intentions
function setId(id);
// what is ID?
// should I check docs?
// should I call author?
// should I try number?
// should I guess?
Manifesting intentions
function setId(id: string);
FREEING MIND
FREEING MIND
enum DevThings {DESIGN, TYPES, TYPE_CHECKING, CODE};
const HEAD: DevThings[] = [];
const COMPILER: DevThings[] = [];
const RUNTIME: DevThings[] = [];
const CODEBASE: DevThings[] = [];
FREEING MIND
enum DevThings {DESIGN, TYPES, TYPE_CHECKING, CODE};
const HEAD: DevThings[] = [];
const COMPILER: DevThings[] = [];
const RUNTIME: DevThings[] = [];
const CODEBASE: DevThings[] = [];
// no code typing?
HEAD.push(DevThings.DESIGN);
HEAD.push(DevThings.TYPES);
HEAD.push(DevThings.TYPE_CHECKING);
RUNTIME.push(DevThings.TYPE_CHECKING);
CODEBASE.push(DevThings.CODE);
FREEING MIND
enum DevThings {DESIGN, TYPES, TYPE_CHECKING, CODE};
const HEAD: DevThings[] = [];
const COMPILER: DevThings[] = [];
const RUNTIME: DevThings[] = [];
const CODEBASE: DevThings[] = [];
// save time and CPU ;)
HEAD.push(DevThings.DESIGN);
COMPILER.push(DevThings.TYPE_CHECKING);
CODEBASE.push(DevThings.CODE);
CODEBASE.push(DevThings.TYPES);
DECOMPOSING SYSTEMS
DECOMPOSING SYSTEMS
system A {
function foo() { /* .. */ };
let b = new B();
b.foo(); // I wonder how they called .foo() method
}
DECOMPOSING SYSTEMS
system A {
function foo() { /* .. */ };
let b = new B();
b.foo(); // I wonder how they called .foo() method
}
system B {
function bar() { /* .. */ };
let a = new A();
a.bar(); // I wonder how they called .bar() method
}
DECOMPOSING SYSTEMS
system A {
interface A { /* .. */ };
function foo() { /* .. */ };
let b: B = new B();
b.bar();
}
system B {
interface B { /* .. */ };
function bar() { /* .. */ };
let a: A = new A();
a.foo();
}
HELPING IDE TO HELP YOU
HELPING IDE TO HELP YOU
IDE and compiler know about types so they can:
- make better refactorings
- auto-complete code
- list proper method names, not guessed ones
- show errors before code execution
- create diagrams
- help you to improve design
- point out common and uncommon mistakes
MAKING TESTS EASIER
MAKING TESTS EASIER
it('checks input types', () => {
expect(() => {
stringsOnly(1);
}).toThrow();
expect(() => {
stringsOnly(true);
}).toThrow();
expect(() => {
stringsOnly([1, 2, 3]);
}).toThrow();
// ...
});
MAKING TESTS EASIER
SIMPLIFYING DESIGN PATTERNS
class BaseStrategy {
foo();
}
class AlternativeStrtegy {
foo();
}
function useStrategy(strategy) {
strategy.foo();
}
SIMPLIFYING DESIGN PATTERNS
class BaseStrategy {
foo();
bar();
}
class AlternativeStrtegy {
foo();
}
function useStrategy(strategy) {
strategy.foo();
strategy.bar();
}
// OOPS.. forgotting about AlternativeStrategy..
SIMPLIFYING DESIGN PATTERNS
interface Strategy {
foo();
bar();
}
class BaseStrategy implements Startegy {
foo();
bar();
}
class AlternativeStrtegy implements Strategy {
foo();
}
function useStrategy(strategy: Strategy) {
strategy.foo();
strategy.bar();
}
// AlternativeStrategy not forgotten! Compile-time error.
// Class be modified. World is safe now!
Any questions?
THE END
Any questions?
Maybe... what tools/languages would you recommend?
Any questions?
Maybe... what tools/languages would you recommend?
- Flow by Facebook at https://flow.org/
- TypeScript by Microsoft at https://www.typescriptlang.org/
- Dart by Google at https://www.dartlang.org/
- Kotlin by JetBrains at https://kotlinlang.org/
TypeScript
- made by Microsoft
- works with JS
- separate but sensitive
- easy to use for JS programmers
Flow
Dart
- made by Google
- separate language
- closed system
- keeps the code in leash better
Kotlin
- made by JetBrains
- multi-platform language
- enables future-proof designs
- keeps the code in leash better
THE END
Any questions?
Maybe... where to find guides for TypeScript?
Any questions?
Maybe... where to find guides for TypeScript?
- Official TypeScript webpage
- TypeScript Deep Dive
THE END
Any questions?
Maybe... how to avoid `any` type?
Any questions?
Maybe... how to avoid `any` type?
// BAD
function stringify(sth: any) { /* ... */ };
Any questions?
Maybe... how to avoid `any` type?
// BETTER
function stringify(sth: string) { /* ... */ };
function stringify(sth: number) { /* ... */ };
function stringify(sth: boolean) { /* ... */ };
function stringify(sth: null) { /* ... */ };
function stringify(sth: Array<any>) { /* ... */ };
function stringify(sth: {[key: string]: any}) { /* ... */ };
Any questions?
Maybe... how to avoid `any` type?
// BEST
type JsonType = JsonPrimitive | JsonArray | JsonObject;
type JsonPrimitive = string | number | boolean | null;
interface JsonObject {
[key: string]: JsonType
}
interface JsonArray {
[key: number]: JsonType
}
function stringify(sth: JsonType): string { /* ... */ };
THE END
Any questions?
Maybe... how to use `any` type?
Any questions?
Maybe... how to use `any` type?
// BAD
applyMapper(fn: Function): any;
// A LITTLE BETTER
applyMapper<TOut>(fn: Function): TOut;
// OK
applyMapper<TIn, TOut>(fn: (input: TIn) => TOut): TOut;
// OK WITH BACKWARD COMPATIBILTY
applyMapper<TIn = any, TOut = any>(fn: (input: TIn) => TOut): TOut;
THE END
Any questions?
Maybe... how to deal with `object` type?
Any questions?
Maybe... how to deal with `object` type?
// PROBABLY NOT WHAT YOU WANT TO USE
// this one is just everything...
let o1: Object = anything;
let o2: {} = sameAsObject;
// this one is limited to non-primitives
// but also is too wide
let o3: object = nonPrimitive;
Any questions?
Maybe... how to deal with `object` type?
// PROBABLY YOU WANT THIS ONE FOR JSON-LIKE STRUCTURES
type JsonType = JsonPrimitive | JsonArray | JsonObject;
type JsonPrimitive = string | number | boolean | null;
interface JsonObject {
[key: string]: JsonType
}
interface JsonArray {
[idx: number]: JsonType
}
Any questions?
Maybe... how to deal with `object` type?
// PROBABLY YOU WANT THIS ONE FOR SIMPLE KEY-VALUE MAPS
interface KeyValueMap<V = any> {
[key: string]: V;
}
THE END
Any questions?
Maybe... how to use interfaces in run-time?
Any questions?
Maybe... how to use interfaces in run-time?
// interface is compile-time thing so this will not work
interface X { /* ... */ }
class C implements X { /* ... */ }
registerAsImplementation(X, C);
// TS compiler error:
// 'X' only refers to a type, but is being used as a value here.
Any questions?
Maybe... how to use interfaces in run-time?
// however you may use classes as interfaces...
abstract class X { /* ... */ }
class C implements X { /* ... */ }
registerAsImplementation(X, C);
// WORKS!
// Use fully abstract classes only
// It is not 100% elegant but works well
THE END
Any questions?
Maybe... how to properly use type assertion?
Any questions?
Maybe... how to properly use type assertion?
declare function foo(): SomeType | OtherType;
// BAD
(foo() as SomeType).someMethod();
Any questions?
Maybe... how to properly use type assertion?
declare function foo(): SomeType | OtherType;
// OK
function isSomeType(obj: SomeType | OtherType): obj is SomeType {
return typeof (obj as SomeType).someMethod === 'function';
}
let fooValue = foo();
if( isSomeType(fooValue) ) {
fooValue.someMethod();
}
THE END
Any questions?
Really last call.. any questions?
THE END
Type the Script
By Lukasz K
Type the Script
- 347