SlideShare a Scribd company logo
© Instil Software 2020
Not Your Mothers TDD
Type Driven Development in Typescript
Richard Gibson
Garth Gilmour
Garth (@GarthGilmour)
Richard (@rickityg)
Type Driven Development with TypeScript
TS is a superset of JavaScript
• Created by Anders Hejlsberg
Coding in TS enables you to:
• Use the features defined in ES 2015+
• Add types to variables and functions
• Use enums, interfaces, generics etc.
Frameworks like Angular are built on TS
• In particular its support for decorators
• React etc. can benefit from TS
The TypeScript Language
TypeScript
ES6
ES5
Things are about to get weird...
– Two types are identical if they have the same structure
– Even if they are named and defined in unrelated places
– In OO this means the same fields and methods
– This is close to the concept of Duck Typing
– Found in dynamic languages like Python and Ruby
– But the compiler is checking your code at build time
TypeScript is Structural not Nominal
It’s all about the shape of the type...
type Pair = {
first: string,
second: number
};
interface Tuple2 {
first: string;
second: number;
}
class Dyad {
constructor( public first: string,
public second: number) {}
}
An Example of Structural Typing
Three types with
the same shape
function test1(input: Pair) {
console.log(`test1 called with ${input.first} and ${input.second}`);
}
function test2(input: Tuple2) {
console.log(`test2 called with ${input.first} and ${input.second}`);
}
function test3(input: Dyad) {
console.log(`test3 called with ${input.first} and ${input.second}`);
}
An Example of Structural Typing
export function structuralTyping() {
let sample1 = { first: "wibble", second: 1234 };
let sample2 = new Dyad("wobble", 5678);
test1(sample1);
test1(sample2);
test2(sample1);
test2(sample2);
test3(sample1);
test3(sample2);
}
An Example of Structural Typing
test1 takes a Pair
test2 takes a Tuple2
test3 takes a Dyad
------ Structural Typing ------
test1 called with wibble and 1234
test1 called with wobble and 5678
test2 called with wibble and 1234
test2 called with wobble and 5678
test3 called with wibble and 1234
test3 called with wobble and 5678
– Complex types can be declared via Type Aliases
– Union Types specify a given type belongs to a set
– Intersection Types combine multiple types together
– String, boolean and number literals can be used as Literal Types
The Weirdness Continues
Some other fun features...
type MyCallback
= (a: string, b:number, c:boolean) => Map<string, boolean>
type MyData = [string, number, boolean];
function sample(func: MyCallback,
data: MyData): Map<string, boolean> {
return func(...data);
}
Type Aliases
these are type aliases
note the spread operator
export function showTypeAliases() {
const data1: MyData = ["abc", 5, false];
const data2: MyData = ["def", 50, true];
const action:MyCallback = (p1, p2, p3) => new Map([
["foo", p1 == "def"],
["bar", p2 > 10],
["zed", p3]
]);
console.log(sample(action, data1));
console.log(sample(action, data2));
}
Type Aliases
compiler ensures correctness
Type Aliases
type Point = {
x: number,
y: number
};
type RetVal = string | Point | Element;
Union Types
note the three options
function demo(input: number): RetVal {
let output: RetVal = {x:20, y:40};
if(input < 50) {
output = "qwerty";
} else if(input < 100) {
output = document.createElement("div");
}
return output;
}
Union Types
might return Point
might return string
might return Node
export function showUnionTypes() {
console.log(demo(40));
console.log(demo(80));
console.log(demo(120));
let result = demo(90);
if(result instanceof Element) {
result.appendChild(document.createTextNode("Hello"));
console.log(result);
}
}
Union Types
no cast required
type Individual = {
think: (topic: string) => string,
feel: (emotion: string) => void
};
type Machine = {
charge: (amount: number) => void,
work: (task: string) => boolean
};
type Cylon = Individual & Machine;
Intersection Types
note the combination
const boomer: Cylon = {
charge(amount: number): void {},
feel(emotion: string): void {},
think(topic: string): string {
return "Kill all humans!";
},
work(task: string): boolean {
return false;
}
};
Intersection Types
type Homer = "Homer";
type Simpsons = Homer | "Marge" | "Bart" | "Lisa" | "Maggie";
type Flintstones = "Fred" | "Wilma" | "Pebbles";
type Evens = 2 | 4 | 6 | 8 | 10;
type Odds = 1 | 3 | 5 | 7 | 9;
Type Literal Values
assembling types from string literals
assembling types from number literals
function demo1(input: Simpsons | Evens) {
console.log(input);
}
function demo2(input: Flintstones | Odds) {
console.log(input);
}
Type Literal Values
Union Type built from Type Literals
export function showTypeLiterals() {
demo1("Homer");
demo1(6);
//demo1("Betty");
//demo1(7);
demo2("Wilma");
demo2(7);
//demo2("Homer");
//demo2(6);
}
Type Literal Values
this is fine
this is fine
will not compile
will not compile
The Warm-Up Is Over
Now for the main event...
Mapped Types
– Mapped Types let us define the shape of a new type
– Based on the structure of one or more existing ones
– You frequently do this with values at runtime
– E.g. staff.map(emp => { emp.salary, emp.dept })
– Consider the ‘three tree problem’
– Where you need three views of the abstraction
– For the UI, Problem Domain and Database
Mapped Types
Defining new types based on old
type InstilReadOnly<T> = {
readonly [K in keyof T]: T[K];
};
type InstilMutable<T> = {
-readonly [K in keyof T]: T[K];
};
type InstilPartial<T> = {
[K in keyof T]?: T[K];
};
type InstilRequired<T> = {
[K in keyof T]-?: T[K];
};
Creating Mapped Types
new type based on T
...but all properties immutable
new type based on T
...but all properties mutable
new type based on T
...but all properties optional
new type based on T
...but all properties mandatory
const person = new Person("Jane", 34, false);
const constantPerson: InstilReadOnly<Person> = person;
const mutablePerson: InstilMutable<Person> = constantPerson;
person.name = "Dave";
//constantPerson.name = "Mary";
mutablePerson.name = "Mary";
Creating Mapped Types
will not compile
fine now
let partialCustomer: InstilPartial<Customer>;
let fullCustomer: InstilRequired<Customer>;
partialCustomer = person;
partialCustomer = {name: "Robin"};
fullCustomer = {
...person,
makeOrder() {}
}
Creating Mapped Types
a person is a partial customer
so is this object literal
we can create a full customer
type Stringify<T> = {
[K in keyof T]: string;
};
type StringifyFields<T> = {
[K in keyof T]: T[K] extends Function ? T[K] : string;
};
Distinguishing Fields From Methods
new type based on T
...but all properties are strings
methods are now excluded
function testStringify() {
const customer: Stringify<Customer> = {
name: "Jason",
age: "27",
married: "true",
makeOrder: "whoops"
};
return customer;
}
Distinguishing Fields From Methods
only fields should be strings
function testStringifyFields() {
const customer: StringifyFields<Customer> = {
name: "Jason",
age: "27",
married: "true",
makeOrder() {
console.log("Order placed by: ", this.name);
}
};
return customer;
}
Distinguishing Fields From Methods
problem solved 
– We can also work with return types at compile time
– Say we have a function which returns an A, B or C
– Where A, B and C are completely unrelated types
– We know we will be testing the type at runtime
– We want the compiler to strongly type the return value
– Based on what is known about the type when we do the return
Compile Space Voodoo Pt.1a
Strongly typing disparate outputs
export function showManagingReturnTypes() {
const data1 = new Centimetres(1000);
const data2 = new Inches(1000);
//input was Centimetres so output is Inches
const result1 = convert(data1).inYards();
//input was Inches so output is Centimetres
const result2 = convert(data2).inMetres();
console.log("1000 centimetres is", result1.toFixed(2), "yards");
console.log("1000 inches is", result2.toFixed(2), "metres");
}
Strongly Typing Outputs
Inches returned
Centimetres
returned
type CentimetresOrInches = Centimetres | Inches;
type CentimetresOrInchesToggle<T extends CentimetresOrInches>
= T extends Centimetres ? Inches : Centimetres;
Strongly Typing Outputs
if T is Inches then CentimetresOrInchesToggle will
be Centimetres (at compile time) and vice versa
our function will return Centimetres or Inches
function convert<T extends CentimetresOrInches>(input: T):
CentimetresOrInchesToggle<T> {
if (input instanceof Centimetres) {
let inches = new Inches(input.amount / 2.54);
return inches as CentimetresOrInchesToggle<T>;
}
let centimetres = new Centimetres(input.amount * 2.54);
return centimetres as CentimetresOrInchesToggle<T>;
}
Strongly Typing Outputs
WAT?
compiler is certain ‘input’ is Centimetres,
so the return type should be inches
compiler is certain ‘input’ is Inches, so
the return type should be Centimetres
– The last demo could have been achieved with overloading
– DOM coding is a more practical example of where it is useful
– A call to ‘document.createElement’ returns a node
– But it would be great to have stricter typing on the result
– So (for example) you could only set ‘source’ on a Video
Compile Space Voodoo Pt.1b
A practical example of typing outputs
type HtmlElements = {
"p": HTMLBodyElement,
"label": HTMLLabelElement,
"canvas": HTMLCanvasElement
}
type ResultElement<T extends string> =
T extends keyof HtmlElements ? HtmlElements[T] : HTMLElement;
Typing HTML Elements
create a type mapping that
associates HTML tags with the
corresponding DOM types
select the correct DOM Node type at compile time
function createElementWithID<T extends string>
(name: T, id: string): ResultElement<T> {
const element = document.createElement(name);
element.id = id;
return element as ResultElement<T>;
}
Typing HTML Elements
returning a strongly typed result
export function showTypingHtmlElements() {
const para = createElementWithID("p", "e1");
const label = createElementWithID("label", "e2");
const canvas = createElementWithID("canvas", "e3");
para.innerText = "Paragraphs have content";
label.htmlFor = "other";
canvas.height = 100;
console.log(para);
console.log(label);
console.log(canvas);
}
Typing HTML Elements
Results strongly typed
– We can extract the types of parameters at compile time
– This gets a little weird
– We can't iterate over the names of the parameters
– So we need to resort to techniques like recursive types
Compile Space Voodoo Pt.2
Working with parameters
type AllParams<T extends (...args: any[]) => any> =
T extends ((...args: infer A) =>any) ? A : never;
type FirstParam<T extends any[]> =
T extends [any, ...any[]] ? T[0] : never;
type OtherParams<T extends any[]> =
((...things: T) => any) extends
((first: any, ...others: infer R) => any) ? R : [];
Working With Parameters
WAT?
function demo(p1: string, p2: number, p3: boolean) {
console.log("Demo called with", p1, p2, "and", p3);
}
export function showWorkingWithParameters() {
const var1: AllParams<typeof demo> = ["abc", 123, false];
const var2: FirstParam<AllParams<typeof demo>> = ”def";
const var3: OtherParams<AllParams<typeof demo>> = [456, true];
demo(...var1);
demo(var2, ...var3);
}
Working With Parameters
type AllParams<T extends (...args: any[]) => any> =
T extends ((...args: infer A) =>any) ? A : never;
Working With Parameters
all the parameter types from a function
type FirstParam<T extends any[]> =
T extends [any, ...any[]] ? T[0] : never;
Working With Parameters
the first parameter type from a function
type OtherParams<T extends any[]> =
((...things: T) => any) extends
((first: any, ...others: infer R) => any) ? R : [];
Working With Parameters
the other parameter types from a function
– Let’s try to implement Partial Invocation
– This is where we take an N argument function and produce
– A function that takes a single argument
...which returns a function that takes the other arguments
...which returns the result
Compile Space Voodoo Pt.3
Typing Partial Invocation
WAT?
function findAllMatches(regex: RegExp,
source: string,
output: Array<string>): Array<string> {
let match: RegExpExecArray | null = null;
while((match = regex.exec(source)) !== null) {
output.push(match[0]);
}
return output;
}
Partial Invocation Applied
const regex = new RegExp("[A-Z]{3}","g");
const data1 = "abcDEFghiJKLmno";
const data2 = "ABCdefGHIkjlMNO";
const results1 = findAllMatches(regex, data1, []);
const results2 = findAllMatches(regex, data2, []);
console.log(results1);
console.log(results2);
Partial Invocation Applied
note the duplication
const findThreeUppercase = partial(findAllMatches)(regex);
const results3 = findThreeUppercase(data1, []);
const results4 = findThreeUppercase(data2, []);
console.log(results3);
console.log(results4);
Partial Invocation Applied
duplication removed
via partial invocation
Partial Invocation
(Iteration 1)
type AnyFunc = (...args: any[]) => any;
type PartiallyInvoke<T extends AnyFunc> =
T extends ((...args: infer A) => infer R)
? ((first: FirstParam<A>) => (x: OtherParams<AllParams<T>>) => R)
: never;
Typing Partial Invocation
first attempt at a return type, using
‘FirstParam’ and ‘OtherParams’
function partial<T extends AnyFunc>(func: T): PartiallyInvoke<T> {
return ((first) => (...others) => func(first, ...others))
as PartiallyInvoke<T>;
}
Typing Partial Invocation
standard JavaScript solution,
but with strong typing added
function test1(x: string, y: number, z: boolean): string {
console.log("Demo called with ", x, y, " and ", z);
return "Foobar";
}
function test2(x: number, y: boolean, z: string): number {
console.log("Test 2 called with ", x, y, " and ", z);
return 123;
}
Typing Partial Invocation
export function showPartialApplicationBroken() {
const f1 = partial(test1);
const f2 = partial(test2);
const result1 = f1("abc")([123,true]);
const result2 = f2(123)([false,"abc"]);
console.log(result1);
console.log(result2);
}
Typing Partial Invocation
very close but not quite
... OtherParams produces a tuple
Typing Partial Invocation
tuples appear as arrays at runtime
...the last parameter is undefined
This Was Slightly Frustrating...
Type Driven Development with TypeScript
Partial Invocation
(Iteration 2)
type PartiallyInvoke<T extends AnyFunc> =
T extends ((...args: infer A) => infer R)
? ((first: FirstParam<A>) => Remainder<T>)
: never;
function partial<T extends AnyFunc>(func: T): PartiallyInvoke<T> {
return ((first) => (...others) => func(first, ...others))
as PartiallyInvoke<T>;
}
Typing Partial Invocation (Iteration 2)
now using a ‘Remainder’ type
type Remainder<T extends AnyFunc> =
T extends ((...args: infer A) => infer R)
? A extends [infer P1, infer P2]
? (x:P2) => R
: A extends [infer P1, infer P2, infer P3]
? ((x:P2, y:P3) => R)
: A extends [infer P1, infer P2, infer P3, infer P4]
? ((x:P2, y:P3, z:P4) => R)
: never
: never;
Typing Partial Invocation (Iteration 2)
WAT?
type Remainder<T extends AnyFunc> =
T extends ((...args: infer A) => infer R)
? A extends [infer P1, infer P2]
? (x:P2) => R
Typing Partial Invocation (Iteration 2)
if the original function took two inputs
then disregard the first and use the second
type Remainder<T extends AnyFunc> =
T extends ((...args: infer A) => infer R)
? A extends [infer P1, infer P2]
? (x:P2) => R
: A extends [infer P1, infer P2, infer P3]
? ((x:P2, y:P3) => R)
Typing Partial Invocation (Iteration 2)
if the original function took three inputs
then disregard the first and use the others
type Remainder<T extends AnyFunc> =
T extends ((...args: infer A) => infer R)
? A extends [infer P1, infer P2]
? (x:P2) => R
: A extends [infer P1, infer P2, infer P3]
? ((x:P2, y:P3) => R)
: A extends [infer P1, infer P2, infer P3, infer P4]
? ((x:P2, y:P3, z:P4) => R)
Typing Partial Invocation (Iteration 2)
extend as necessary
export function showPartialApplicationImproved() {
const f1 = partial(test1);
const f2 = partial(test2);
const result1 = f1("abc")(123, true);
const result2 = f2(123)(false, "abc");
console.log(result1);
console.log(result2);
}
Typing Partial Invocation (Iteration 2)
This Made Me Happy...
– What we have been doing is ‘coding at compile time’
– We have been persuading the TypeScript compiler to create new types and
make inferences for us at compile time
– We have seen that we can make choices
– Via the ternary conditional operator
– There is no support for the procedural loops
– However we can use recursion to iterate at compile time
Compile Space Voodoo Pt.4
Recursive Types
Compile Space Voodoo Pt.4
We can use recursion to iterate at compile time!!!
type IncTable = {
0: 1;
1: 2;
2: 3;
3: 4;
4: 5;
5: 6;
6: 7;
7: 8;
8: 9;
9: 10
};
export type Inc<T extends number>
= T extends keyof IncTable ? IncTable[T] : never;
Recursive Types (Numbers)
what do you think Inc<6> would be?
type DecTable = {
10: 9;
9: 8;
8: 7;
7: 6;
6: 5;
5: 4;
4: 3;
3: 2;
2: 1;
1: 0
};
export type Dec<T extends number>
= T extends keyof DecTable ? DecTable[T] : never;
Recursive Types (Numbers)
what do you think Dec<5> would be?
export type Add<A extends number, B extends number> = {
again: Add<Inc<A>, Dec<B>>
return: A
}[B extends 0 ? "return" : "again"]
Recursive Types (Numbers)
WAT?
recursively increment A whilst also
decrementing B until the latter is 0
export type Add<A extends number, B extends number> = {
again: Add<Inc<A>, Dec<B>>
return: A
}[B extends 0 ? "return" : "again"]
Recursive Types (Numbers)
Add<5, 3>
Add<Inc<5>, Dec<3>>
Add<Inc<6>, Dec<2>>
Add<Inc<7>, Dec<1>>
Add<Inc<8>, Dec<0>>
8
type SampleList = [boolean, number, string];
export type Head<T extends any[]> =
T extends [any, ...any[]] ? T[0] : never;
export type Rest<T extends any[]> =
((...t: T) => any) extends ((_: any, ...tail: infer TT) => any)
? TT : []
Recursive Types (Lists)
use the spread operator to
extract the first list item
use the spread operator and function declaration
syntax to extract the remainder of the list
type SampleList = [boolean, number, string];
export type LengthByProperty<T extends any[]> = T['length']
export type LengthByRecursion<T extends any[],
R extends number = 0> = {
again: LengthByRecursion<Rest<T>, Inc<R>>
return: R
}[ T extends [] ? "return" : "again" ]
Recursive Types (Lists)
calculate the length of a list at
compile time in two ways
export type Prepend<E, T extends any[]> =
// assign [E, ...T] to U
((head: E, ...args: T) => any) extends ((...args: infer U) => any)
? U
: never //never reached
Recursive Types (Lists)
use the spread operator and function
declaration syntax to prepend a type
type SampleList = [boolean, number, string];
export type Reverse<T extends any[],
R extends any[] = [],
I extends number = 0> = {
again: Reverse<T, Prepend<T[I], R>, Inc<I>>
return: R
}[ I extends LengthByRecursion<T> ? "return" : "again" ]
Recursive Types (Lists)
export function showRecursiveTypesWithLists() {
type SampleList = [boolean, number, string];
const data1: Head<SampleList> = true;
const data2: Rest<SampleList> = [12, "abc"];
const data3: LengthByRecursion<SampleList> = 3;
const data4: Reverse<SampleList> = ["def", 123, false];
console.log(data1);
console.log(data2);
console.log(data3);
console.log(data4);
}
Recursive Types (Lists)
Applying Recursive Types
https://www.youtube.com/watch?v=GFcQSQboBsM
Conclusions
We’ve Been Coding In Type Space
– In Test Driven Development we work from the outside in
– The tests cannot write the implementation on our behalf
– But they constrain our choices and point us the right way
– Type Driven Development works the same way
– We are not doing strong typing just to catch errors
– Instead the compiler guides us to the right solution
What is Type Driven Development?
...and why should you try it?
https://bitbucket.org/instilco/nidc-october-2020
https://www.youtube.com/watch?v=GFcQSQboBsM
Examples from this slide deck
The coding demo
Questions?

More Related Content

What's hot (20)

Cryptography 101 for Java Developers - JavaZone2019
Cryptography 101 for Java Developers - JavaZone2019
Michel Schudel
 
Playing Video with ExoPlayer : Android Bangkok 2018
Playing Video with ExoPlayer : Android Bangkok 2018
Minseo Chayabanjonglerd
 
Web Development with Python and Django
Web Development with Python and Django
Michael Pirnat
 
String classes and its methods.20
String classes and its methods.20
myrajendra
 
Java script ppt
Java script ppt
The Health and Social Care Information Centre
 
Building RESTful applications using Spring MVC
Building RESTful applications using Spring MVC
IndicThreads
 
python presntation 2.pptx
python presntation 2.pptx
Arpittripathi45
 
Control Structures In Php 2
Control Structures In Php 2
Digital Insights - Digital Marketing Agency
 
Java11 New Features
Java11 New Features
Haim Michael
 
A Basic Django Introduction
A Basic Django Introduction
Ganga Ram
 
Java programming lab manual
Java programming lab manual
sameer farooq
 
Selenium cheat sheet
Selenium cheat sheet
Sri Priya P Kulkarni
 
Introduction to EJB
Introduction to EJB
Return on Intelligence
 
Jquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript Basics
EPAM Systems
 
Java Socket Programming
Java Socket Programming
Vipin Yadav
 
1. Arrow Functions | JavaScript | ES6
1. Arrow Functions | JavaScript | ES6
pcnmtutorials
 
Constructor in java
Constructor in java
Madishetty Prathibha
 
Lightweight static code analysis with semgrep
Lightweight static code analysis with semgrep
Null Bhubaneswar
 
Py.test
Py.test
soasme
 
Collections and its types in C# (with examples)
Collections and its types in C# (with examples)
Aijaz Ali Abro
 
Cryptography 101 for Java Developers - JavaZone2019
Cryptography 101 for Java Developers - JavaZone2019
Michel Schudel
 
Playing Video with ExoPlayer : Android Bangkok 2018
Playing Video with ExoPlayer : Android Bangkok 2018
Minseo Chayabanjonglerd
 
Web Development with Python and Django
Web Development with Python and Django
Michael Pirnat
 
String classes and its methods.20
String classes and its methods.20
myrajendra
 
Building RESTful applications using Spring MVC
Building RESTful applications using Spring MVC
IndicThreads
 
python presntation 2.pptx
python presntation 2.pptx
Arpittripathi45
 
Java11 New Features
Java11 New Features
Haim Michael
 
A Basic Django Introduction
A Basic Django Introduction
Ganga Ram
 
Java programming lab manual
Java programming lab manual
sameer farooq
 
Jquery Complete Presentation along with Javascript Basics
Jquery Complete Presentation along with Javascript Basics
EPAM Systems
 
Java Socket Programming
Java Socket Programming
Vipin Yadav
 
1. Arrow Functions | JavaScript | ES6
1. Arrow Functions | JavaScript | ES6
pcnmtutorials
 
Lightweight static code analysis with semgrep
Lightweight static code analysis with semgrep
Null Bhubaneswar
 
Py.test
Py.test
soasme
 
Collections and its types in C# (with examples)
Collections and its types in C# (with examples)
Aijaz Ali Abro
 

Similar to Type Driven Development with TypeScript (20)

Back to the Future with TypeScript
Back to the Future with TypeScript
Aleš Najmann
 
Introduction to TypeScript
Introduction to TypeScript
Tomas Corral Casas
 
Introduction to typescript
Introduction to typescript
Mario Alexandro Santini
 
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
Alfonso Peletier
 
Type script is awesome
Type script is awesome
KeithMurgic
 
TypeScript Introduction
TypeScript Introduction
Hans Höchtl
 
Introduction to TypeScript
Introduction to TypeScript
KeithMurgic
 
Types For Frontend Developers
Types For Frontend Developers
Jesse Williamson
 
Static types on javascript?! Type checking approaches to ensure healthy appli...
Static types on javascript?! Type checking approaches to ensure healthy appli...
Arthur Puthin
 
Why TypeScript?
Why TypeScript?
FITC
 
TypeScript.ppt LPU Notes Lecture PPT for 2024
TypeScript.ppt LPU Notes Lecture PPT for 2024
manveersingh2k05
 
Type script - advanced usage and practices
Type script - advanced usage and practices
Iwan van der Kleijn
 
TypeScript by Howard
TypeScript by Howard
LearningTech
 
Howard type script
Howard type script
LearningTech
 
Type script by Howard
Type script by Howard
LearningTech
 
TypeScript Interview Questions PDF By ScholarHat
TypeScript Interview Questions PDF By ScholarHat
Scholarhat
 
Typescript barcelona
Typescript barcelona
Christoffer Noring
 
"Enhancing Your React with Advanced TypeScript", Titian Cernicova-Dragomir
"Enhancing Your React with Advanced TypeScript", Titian Cernicova-Dragomir
Fwdays
 
Introduction to TypeScript
Introduction to TypeScript
André Pitombeira
 
Complete Notes on Angular 2 and TypeScript
Complete Notes on Angular 2 and TypeScript
EPAM Systems
 
Back to the Future with TypeScript
Back to the Future with TypeScript
Aleš Najmann
 
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
TypeScript - All you ever wanted to know - Tech Talk by Epic Labs
Alfonso Peletier
 
Type script is awesome
Type script is awesome
KeithMurgic
 
TypeScript Introduction
TypeScript Introduction
Hans Höchtl
 
Introduction to TypeScript
Introduction to TypeScript
KeithMurgic
 
Types For Frontend Developers
Types For Frontend Developers
Jesse Williamson
 
Static types on javascript?! Type checking approaches to ensure healthy appli...
Static types on javascript?! Type checking approaches to ensure healthy appli...
Arthur Puthin
 
Why TypeScript?
Why TypeScript?
FITC
 
TypeScript.ppt LPU Notes Lecture PPT for 2024
TypeScript.ppt LPU Notes Lecture PPT for 2024
manveersingh2k05
 
Type script - advanced usage and practices
Type script - advanced usage and practices
Iwan van der Kleijn
 
TypeScript by Howard
TypeScript by Howard
LearningTech
 
Howard type script
Howard type script
LearningTech
 
Type script by Howard
Type script by Howard
LearningTech
 
TypeScript Interview Questions PDF By ScholarHat
TypeScript Interview Questions PDF By ScholarHat
Scholarhat
 
"Enhancing Your React with Advanced TypeScript", Titian Cernicova-Dragomir
"Enhancing Your React with Advanced TypeScript", Titian Cernicova-Dragomir
Fwdays
 
Complete Notes on Angular 2 and TypeScript
Complete Notes on Angular 2 and TypeScript
EPAM Systems
 

More from Garth Gilmour (20)

Compose in Theory
Compose in Theory
Garth Gilmour
 
Kotlin / Android Update
Kotlin / Android Update
Garth Gilmour
 
TypeScript Vs. KotlinJS
TypeScript Vs. KotlinJS
Garth Gilmour
 
Shut Up And Eat Your Veg
Shut Up And Eat Your Veg
Garth Gilmour
 
Lies Told By The Kotlin Compiler
Lies Told By The Kotlin Compiler
Garth Gilmour
 
A TypeScript Fans KotlinJS Adventures
A TypeScript Fans KotlinJS Adventures
Garth Gilmour
 
The Heat Death Of Enterprise IT
The Heat Death Of Enterprise IT
Garth Gilmour
 
Lies Told By The Kotlin Compiler
Lies Told By The Kotlin Compiler
Garth Gilmour
 
Generics On The JVM (What you don't know will hurt you)
Generics On The JVM (What you don't know will hurt you)
Garth Gilmour
 
Using Kotlin, to Create Kotlin, to Teach Kotlin, in Space
Using Kotlin, to Create Kotlin, to Teach Kotlin, in Space
Garth Gilmour
 
Is Software Engineering A Profession?
Is Software Engineering A Profession?
Garth Gilmour
 
Social Distancing is not Behaving Distantly
Social Distancing is not Behaving Distantly
Garth Gilmour
 
The Great Scala Makeover
The Great Scala Makeover
Garth Gilmour
 
Transitioning Android Teams Into Kotlin
Transitioning Android Teams Into Kotlin
Garth Gilmour
 
Simpler and Safer Java Types (via the Vavr and Lambda Libraries)
Simpler and Safer Java Types (via the Vavr and Lambda Libraries)
Garth Gilmour
 
The Three Horse Race
The Three Horse Race
Garth Gilmour
 
The Bestiary of Pure Functional Programming
The Bestiary of Pure Functional Programming
Garth Gilmour
 
BelTech 2019 Presenters Workshop
BelTech 2019 Presenters Workshop
Garth Gilmour
 
Kotlin The Whole Damn Family
Kotlin The Whole Damn Family
Garth Gilmour
 
The Philosophy of DDD
The Philosophy of DDD
Garth Gilmour
 
Kotlin / Android Update
Kotlin / Android Update
Garth Gilmour
 
TypeScript Vs. KotlinJS
TypeScript Vs. KotlinJS
Garth Gilmour
 
Shut Up And Eat Your Veg
Shut Up And Eat Your Veg
Garth Gilmour
 
Lies Told By The Kotlin Compiler
Lies Told By The Kotlin Compiler
Garth Gilmour
 
A TypeScript Fans KotlinJS Adventures
A TypeScript Fans KotlinJS Adventures
Garth Gilmour
 
The Heat Death Of Enterprise IT
The Heat Death Of Enterprise IT
Garth Gilmour
 
Lies Told By The Kotlin Compiler
Lies Told By The Kotlin Compiler
Garth Gilmour
 
Generics On The JVM (What you don't know will hurt you)
Generics On The JVM (What you don't know will hurt you)
Garth Gilmour
 
Using Kotlin, to Create Kotlin, to Teach Kotlin, in Space
Using Kotlin, to Create Kotlin, to Teach Kotlin, in Space
Garth Gilmour
 
Is Software Engineering A Profession?
Is Software Engineering A Profession?
Garth Gilmour
 
Social Distancing is not Behaving Distantly
Social Distancing is not Behaving Distantly
Garth Gilmour
 
The Great Scala Makeover
The Great Scala Makeover
Garth Gilmour
 
Transitioning Android Teams Into Kotlin
Transitioning Android Teams Into Kotlin
Garth Gilmour
 
Simpler and Safer Java Types (via the Vavr and Lambda Libraries)
Simpler and Safer Java Types (via the Vavr and Lambda Libraries)
Garth Gilmour
 
The Three Horse Race
The Three Horse Race
Garth Gilmour
 
The Bestiary of Pure Functional Programming
The Bestiary of Pure Functional Programming
Garth Gilmour
 
BelTech 2019 Presenters Workshop
BelTech 2019 Presenters Workshop
Garth Gilmour
 
Kotlin The Whole Damn Family
Kotlin The Whole Damn Family
Garth Gilmour
 
The Philosophy of DDD
The Philosophy of DDD
Garth Gilmour
 

Recently uploaded (20)

Software Testing & it’s types (DevOps)
Software Testing & it’s types (DevOps)
S Pranav (Deepu)
 
Porting Qt 5 QML Modules to Qt 6 Webinar
Porting Qt 5 QML Modules to Qt 6 Webinar
ICS
 
wAIred_RabobankIgniteSession_12062025.pptx
wAIred_RabobankIgniteSession_12062025.pptx
SimonedeGijt
 
Artificial Intelligence Applications Across Industries
Artificial Intelligence Applications Across Industries
SandeepKS52
 
Agentic Techniques in Retrieval-Augmented Generation with Azure AI Search
Agentic Techniques in Retrieval-Augmented Generation with Azure AI Search
Maxim Salnikov
 
Migrating to Azure Cosmos DB the Right Way
Migrating to Azure Cosmos DB the Right Way
Alexander (Alex) Komyagin
 
Milwaukee Marketo User Group June 2025 - Optimize and Enhance Efficiency - Sm...
Milwaukee Marketo User Group June 2025 - Optimize and Enhance Efficiency - Sm...
BradBedford3
 
How the US Navy Approaches DevSecOps with Raise 2.0
How the US Navy Approaches DevSecOps with Raise 2.0
Anchore
 
OpenTelemetry 101 Cloud Native Barcelona
OpenTelemetry 101 Cloud Native Barcelona
Imma Valls Bernaus
 
dp-700 exam questions sample docume .pdf
dp-700 exam questions sample docume .pdf
pravkumarbiz
 
How Insurance Policy Management Software Streamlines Operations
How Insurance Policy Management Software Streamlines Operations
Insurance Tech Services
 
Zoneranker’s Digital marketing solutions
Zoneranker’s Digital marketing solutions
reenashriee
 
Software Engineering Process, Notation & Tools Introduction - Part 3
Software Engineering Process, Notation & Tools Introduction - Part 3
Gaurav Sharma
 
Async-ronizing Success at Wix - Patterns for Seamless Microservices - Devoxx ...
Async-ronizing Success at Wix - Patterns for Seamless Microservices - Devoxx ...
Natan Silnitsky
 
Step by step guide to install Flutter and Dart
Step by step guide to install Flutter and Dart
S Pranav (Deepu)
 
Neuralink Templateeeeeeeeeeeeeeeeeeeeeeeeee
Neuralink Templateeeeeeeeeeeeeeeeeeeeeeeeee
alexandernoetzold
 
Advanced Token Development - Decentralized Innovation
Advanced Token Development - Decentralized Innovation
arohisinghas720
 
Wondershare PDFelement Pro 11.4.20.3548 Crack Free Download
Wondershare PDFelement Pro 11.4.20.3548 Crack Free Download
Puppy jhon
 
Who will create the languages of the future?
Who will create the languages of the future?
Jordi Cabot
 
Women in Tech: Marketo Engage User Group - June 2025 - AJO with AWS
Women in Tech: Marketo Engage User Group - June 2025 - AJO with AWS
BradBedford3
 
Software Testing & it’s types (DevOps)
Software Testing & it’s types (DevOps)
S Pranav (Deepu)
 
Porting Qt 5 QML Modules to Qt 6 Webinar
Porting Qt 5 QML Modules to Qt 6 Webinar
ICS
 
wAIred_RabobankIgniteSession_12062025.pptx
wAIred_RabobankIgniteSession_12062025.pptx
SimonedeGijt
 
Artificial Intelligence Applications Across Industries
Artificial Intelligence Applications Across Industries
SandeepKS52
 
Agentic Techniques in Retrieval-Augmented Generation with Azure AI Search
Agentic Techniques in Retrieval-Augmented Generation with Azure AI Search
Maxim Salnikov
 
Milwaukee Marketo User Group June 2025 - Optimize and Enhance Efficiency - Sm...
Milwaukee Marketo User Group June 2025 - Optimize and Enhance Efficiency - Sm...
BradBedford3
 
How the US Navy Approaches DevSecOps with Raise 2.0
How the US Navy Approaches DevSecOps with Raise 2.0
Anchore
 
OpenTelemetry 101 Cloud Native Barcelona
OpenTelemetry 101 Cloud Native Barcelona
Imma Valls Bernaus
 
dp-700 exam questions sample docume .pdf
dp-700 exam questions sample docume .pdf
pravkumarbiz
 
How Insurance Policy Management Software Streamlines Operations
How Insurance Policy Management Software Streamlines Operations
Insurance Tech Services
 
Zoneranker’s Digital marketing solutions
Zoneranker’s Digital marketing solutions
reenashriee
 
Software Engineering Process, Notation & Tools Introduction - Part 3
Software Engineering Process, Notation & Tools Introduction - Part 3
Gaurav Sharma
 
Async-ronizing Success at Wix - Patterns for Seamless Microservices - Devoxx ...
Async-ronizing Success at Wix - Patterns for Seamless Microservices - Devoxx ...
Natan Silnitsky
 
Step by step guide to install Flutter and Dart
Step by step guide to install Flutter and Dart
S Pranav (Deepu)
 
Neuralink Templateeeeeeeeeeeeeeeeeeeeeeeeee
Neuralink Templateeeeeeeeeeeeeeeeeeeeeeeeee
alexandernoetzold
 
Advanced Token Development - Decentralized Innovation
Advanced Token Development - Decentralized Innovation
arohisinghas720
 
Wondershare PDFelement Pro 11.4.20.3548 Crack Free Download
Wondershare PDFelement Pro 11.4.20.3548 Crack Free Download
Puppy jhon
 
Who will create the languages of the future?
Who will create the languages of the future?
Jordi Cabot
 
Women in Tech: Marketo Engage User Group - June 2025 - AJO with AWS
Women in Tech: Marketo Engage User Group - June 2025 - AJO with AWS
BradBedford3
 

Type Driven Development with TypeScript

  • 1. © Instil Software 2020 Not Your Mothers TDD Type Driven Development in Typescript Richard Gibson Garth Gilmour
  • 5. TS is a superset of JavaScript • Created by Anders Hejlsberg Coding in TS enables you to: • Use the features defined in ES 2015+ • Add types to variables and functions • Use enums, interfaces, generics etc. Frameworks like Angular are built on TS • In particular its support for decorators • React etc. can benefit from TS The TypeScript Language TypeScript ES6 ES5
  • 6. Things are about to get weird...
  • 7. – Two types are identical if they have the same structure – Even if they are named and defined in unrelated places – In OO this means the same fields and methods – This is close to the concept of Duck Typing – Found in dynamic languages like Python and Ruby – But the compiler is checking your code at build time TypeScript is Structural not Nominal It’s all about the shape of the type...
  • 8. type Pair = { first: string, second: number }; interface Tuple2 { first: string; second: number; } class Dyad { constructor( public first: string, public second: number) {} } An Example of Structural Typing Three types with the same shape
  • 9. function test1(input: Pair) { console.log(`test1 called with ${input.first} and ${input.second}`); } function test2(input: Tuple2) { console.log(`test2 called with ${input.first} and ${input.second}`); } function test3(input: Dyad) { console.log(`test3 called with ${input.first} and ${input.second}`); } An Example of Structural Typing
  • 10. export function structuralTyping() { let sample1 = { first: "wibble", second: 1234 }; let sample2 = new Dyad("wobble", 5678); test1(sample1); test1(sample2); test2(sample1); test2(sample2); test3(sample1); test3(sample2); } An Example of Structural Typing test1 takes a Pair test2 takes a Tuple2 test3 takes a Dyad
  • 11. ------ Structural Typing ------ test1 called with wibble and 1234 test1 called with wobble and 5678 test2 called with wibble and 1234 test2 called with wobble and 5678 test3 called with wibble and 1234 test3 called with wobble and 5678
  • 12. – Complex types can be declared via Type Aliases – Union Types specify a given type belongs to a set – Intersection Types combine multiple types together – String, boolean and number literals can be used as Literal Types The Weirdness Continues Some other fun features...
  • 13. type MyCallback = (a: string, b:number, c:boolean) => Map<string, boolean> type MyData = [string, number, boolean]; function sample(func: MyCallback, data: MyData): Map<string, boolean> { return func(...data); } Type Aliases these are type aliases note the spread operator
  • 14. export function showTypeAliases() { const data1: MyData = ["abc", 5, false]; const data2: MyData = ["def", 50, true]; const action:MyCallback = (p1, p2, p3) => new Map([ ["foo", p1 == "def"], ["bar", p2 > 10], ["zed", p3] ]); console.log(sample(action, data1)); console.log(sample(action, data2)); } Type Aliases compiler ensures correctness
  • 16. type Point = { x: number, y: number }; type RetVal = string | Point | Element; Union Types note the three options
  • 17. function demo(input: number): RetVal { let output: RetVal = {x:20, y:40}; if(input < 50) { output = "qwerty"; } else if(input < 100) { output = document.createElement("div"); } return output; } Union Types might return Point might return string might return Node
  • 18. export function showUnionTypes() { console.log(demo(40)); console.log(demo(80)); console.log(demo(120)); let result = demo(90); if(result instanceof Element) { result.appendChild(document.createTextNode("Hello")); console.log(result); } } Union Types no cast required
  • 19. type Individual = { think: (topic: string) => string, feel: (emotion: string) => void }; type Machine = { charge: (amount: number) => void, work: (task: string) => boolean }; type Cylon = Individual & Machine; Intersection Types note the combination
  • 20. const boomer: Cylon = { charge(amount: number): void {}, feel(emotion: string): void {}, think(topic: string): string { return "Kill all humans!"; }, work(task: string): boolean { return false; } }; Intersection Types
  • 21. type Homer = "Homer"; type Simpsons = Homer | "Marge" | "Bart" | "Lisa" | "Maggie"; type Flintstones = "Fred" | "Wilma" | "Pebbles"; type Evens = 2 | 4 | 6 | 8 | 10; type Odds = 1 | 3 | 5 | 7 | 9; Type Literal Values assembling types from string literals assembling types from number literals
  • 22. function demo1(input: Simpsons | Evens) { console.log(input); } function demo2(input: Flintstones | Odds) { console.log(input); } Type Literal Values Union Type built from Type Literals
  • 23. export function showTypeLiterals() { demo1("Homer"); demo1(6); //demo1("Betty"); //demo1(7); demo2("Wilma"); demo2(7); //demo2("Homer"); //demo2(6); } Type Literal Values this is fine this is fine will not compile will not compile
  • 24. The Warm-Up Is Over Now for the main event...
  • 26. – Mapped Types let us define the shape of a new type – Based on the structure of one or more existing ones – You frequently do this with values at runtime – E.g. staff.map(emp => { emp.salary, emp.dept }) – Consider the ‘three tree problem’ – Where you need three views of the abstraction – For the UI, Problem Domain and Database Mapped Types Defining new types based on old
  • 27. type InstilReadOnly<T> = { readonly [K in keyof T]: T[K]; }; type InstilMutable<T> = { -readonly [K in keyof T]: T[K]; }; type InstilPartial<T> = { [K in keyof T]?: T[K]; }; type InstilRequired<T> = { [K in keyof T]-?: T[K]; }; Creating Mapped Types new type based on T ...but all properties immutable new type based on T ...but all properties mutable new type based on T ...but all properties optional new type based on T ...but all properties mandatory
  • 28. const person = new Person("Jane", 34, false); const constantPerson: InstilReadOnly<Person> = person; const mutablePerson: InstilMutable<Person> = constantPerson; person.name = "Dave"; //constantPerson.name = "Mary"; mutablePerson.name = "Mary"; Creating Mapped Types will not compile fine now
  • 29. let partialCustomer: InstilPartial<Customer>; let fullCustomer: InstilRequired<Customer>; partialCustomer = person; partialCustomer = {name: "Robin"}; fullCustomer = { ...person, makeOrder() {} } Creating Mapped Types a person is a partial customer so is this object literal we can create a full customer
  • 30. type Stringify<T> = { [K in keyof T]: string; }; type StringifyFields<T> = { [K in keyof T]: T[K] extends Function ? T[K] : string; }; Distinguishing Fields From Methods new type based on T ...but all properties are strings methods are now excluded
  • 31. function testStringify() { const customer: Stringify<Customer> = { name: "Jason", age: "27", married: "true", makeOrder: "whoops" }; return customer; } Distinguishing Fields From Methods only fields should be strings
  • 32. function testStringifyFields() { const customer: StringifyFields<Customer> = { name: "Jason", age: "27", married: "true", makeOrder() { console.log("Order placed by: ", this.name); } }; return customer; } Distinguishing Fields From Methods problem solved 
  • 33. – We can also work with return types at compile time – Say we have a function which returns an A, B or C – Where A, B and C are completely unrelated types – We know we will be testing the type at runtime – We want the compiler to strongly type the return value – Based on what is known about the type when we do the return Compile Space Voodoo Pt.1a Strongly typing disparate outputs
  • 34. export function showManagingReturnTypes() { const data1 = new Centimetres(1000); const data2 = new Inches(1000); //input was Centimetres so output is Inches const result1 = convert(data1).inYards(); //input was Inches so output is Centimetres const result2 = convert(data2).inMetres(); console.log("1000 centimetres is", result1.toFixed(2), "yards"); console.log("1000 inches is", result2.toFixed(2), "metres"); } Strongly Typing Outputs Inches returned Centimetres returned
  • 35. type CentimetresOrInches = Centimetres | Inches; type CentimetresOrInchesToggle<T extends CentimetresOrInches> = T extends Centimetres ? Inches : Centimetres; Strongly Typing Outputs if T is Inches then CentimetresOrInchesToggle will be Centimetres (at compile time) and vice versa our function will return Centimetres or Inches
  • 36. function convert<T extends CentimetresOrInches>(input: T): CentimetresOrInchesToggle<T> { if (input instanceof Centimetres) { let inches = new Inches(input.amount / 2.54); return inches as CentimetresOrInchesToggle<T>; } let centimetres = new Centimetres(input.amount * 2.54); return centimetres as CentimetresOrInchesToggle<T>; } Strongly Typing Outputs WAT? compiler is certain ‘input’ is Centimetres, so the return type should be inches compiler is certain ‘input’ is Inches, so the return type should be Centimetres
  • 37. – The last demo could have been achieved with overloading – DOM coding is a more practical example of where it is useful – A call to ‘document.createElement’ returns a node – But it would be great to have stricter typing on the result – So (for example) you could only set ‘source’ on a Video Compile Space Voodoo Pt.1b A practical example of typing outputs
  • 38. type HtmlElements = { "p": HTMLBodyElement, "label": HTMLLabelElement, "canvas": HTMLCanvasElement } type ResultElement<T extends string> = T extends keyof HtmlElements ? HtmlElements[T] : HTMLElement; Typing HTML Elements create a type mapping that associates HTML tags with the corresponding DOM types select the correct DOM Node type at compile time
  • 39. function createElementWithID<T extends string> (name: T, id: string): ResultElement<T> { const element = document.createElement(name); element.id = id; return element as ResultElement<T>; } Typing HTML Elements returning a strongly typed result
  • 40. export function showTypingHtmlElements() { const para = createElementWithID("p", "e1"); const label = createElementWithID("label", "e2"); const canvas = createElementWithID("canvas", "e3"); para.innerText = "Paragraphs have content"; label.htmlFor = "other"; canvas.height = 100; console.log(para); console.log(label); console.log(canvas); } Typing HTML Elements Results strongly typed
  • 41. – We can extract the types of parameters at compile time – This gets a little weird – We can't iterate over the names of the parameters – So we need to resort to techniques like recursive types Compile Space Voodoo Pt.2 Working with parameters
  • 42. type AllParams<T extends (...args: any[]) => any> = T extends ((...args: infer A) =>any) ? A : never; type FirstParam<T extends any[]> = T extends [any, ...any[]] ? T[0] : never; type OtherParams<T extends any[]> = ((...things: T) => any) extends ((first: any, ...others: infer R) => any) ? R : []; Working With Parameters WAT?
  • 43. function demo(p1: string, p2: number, p3: boolean) { console.log("Demo called with", p1, p2, "and", p3); } export function showWorkingWithParameters() { const var1: AllParams<typeof demo> = ["abc", 123, false]; const var2: FirstParam<AllParams<typeof demo>> = ”def"; const var3: OtherParams<AllParams<typeof demo>> = [456, true]; demo(...var1); demo(var2, ...var3); } Working With Parameters
  • 44. type AllParams<T extends (...args: any[]) => any> = T extends ((...args: infer A) =>any) ? A : never; Working With Parameters all the parameter types from a function
  • 45. type FirstParam<T extends any[]> = T extends [any, ...any[]] ? T[0] : never; Working With Parameters the first parameter type from a function
  • 46. type OtherParams<T extends any[]> = ((...things: T) => any) extends ((first: any, ...others: infer R) => any) ? R : []; Working With Parameters the other parameter types from a function
  • 47. – Let’s try to implement Partial Invocation – This is where we take an N argument function and produce – A function that takes a single argument ...which returns a function that takes the other arguments ...which returns the result Compile Space Voodoo Pt.3 Typing Partial Invocation WAT?
  • 48. function findAllMatches(regex: RegExp, source: string, output: Array<string>): Array<string> { let match: RegExpExecArray | null = null; while((match = regex.exec(source)) !== null) { output.push(match[0]); } return output; } Partial Invocation Applied
  • 49. const regex = new RegExp("[A-Z]{3}","g"); const data1 = "abcDEFghiJKLmno"; const data2 = "ABCdefGHIkjlMNO"; const results1 = findAllMatches(regex, data1, []); const results2 = findAllMatches(regex, data2, []); console.log(results1); console.log(results2); Partial Invocation Applied note the duplication
  • 50. const findThreeUppercase = partial(findAllMatches)(regex); const results3 = findThreeUppercase(data1, []); const results4 = findThreeUppercase(data2, []); console.log(results3); console.log(results4); Partial Invocation Applied duplication removed via partial invocation
  • 52. type AnyFunc = (...args: any[]) => any; type PartiallyInvoke<T extends AnyFunc> = T extends ((...args: infer A) => infer R) ? ((first: FirstParam<A>) => (x: OtherParams<AllParams<T>>) => R) : never; Typing Partial Invocation first attempt at a return type, using ‘FirstParam’ and ‘OtherParams’
  • 53. function partial<T extends AnyFunc>(func: T): PartiallyInvoke<T> { return ((first) => (...others) => func(first, ...others)) as PartiallyInvoke<T>; } Typing Partial Invocation standard JavaScript solution, but with strong typing added
  • 54. function test1(x: string, y: number, z: boolean): string { console.log("Demo called with ", x, y, " and ", z); return "Foobar"; } function test2(x: number, y: boolean, z: string): number { console.log("Test 2 called with ", x, y, " and ", z); return 123; } Typing Partial Invocation
  • 55. export function showPartialApplicationBroken() { const f1 = partial(test1); const f2 = partial(test2); const result1 = f1("abc")([123,true]); const result2 = f2(123)([false,"abc"]); console.log(result1); console.log(result2); } Typing Partial Invocation very close but not quite ... OtherParams produces a tuple
  • 56. Typing Partial Invocation tuples appear as arrays at runtime ...the last parameter is undefined
  • 57. This Was Slightly Frustrating...
  • 60. type PartiallyInvoke<T extends AnyFunc> = T extends ((...args: infer A) => infer R) ? ((first: FirstParam<A>) => Remainder<T>) : never; function partial<T extends AnyFunc>(func: T): PartiallyInvoke<T> { return ((first) => (...others) => func(first, ...others)) as PartiallyInvoke<T>; } Typing Partial Invocation (Iteration 2) now using a ‘Remainder’ type
  • 61. type Remainder<T extends AnyFunc> = T extends ((...args: infer A) => infer R) ? A extends [infer P1, infer P2] ? (x:P2) => R : A extends [infer P1, infer P2, infer P3] ? ((x:P2, y:P3) => R) : A extends [infer P1, infer P2, infer P3, infer P4] ? ((x:P2, y:P3, z:P4) => R) : never : never; Typing Partial Invocation (Iteration 2) WAT?
  • 62. type Remainder<T extends AnyFunc> = T extends ((...args: infer A) => infer R) ? A extends [infer P1, infer P2] ? (x:P2) => R Typing Partial Invocation (Iteration 2) if the original function took two inputs then disregard the first and use the second
  • 63. type Remainder<T extends AnyFunc> = T extends ((...args: infer A) => infer R) ? A extends [infer P1, infer P2] ? (x:P2) => R : A extends [infer P1, infer P2, infer P3] ? ((x:P2, y:P3) => R) Typing Partial Invocation (Iteration 2) if the original function took three inputs then disregard the first and use the others
  • 64. type Remainder<T extends AnyFunc> = T extends ((...args: infer A) => infer R) ? A extends [infer P1, infer P2] ? (x:P2) => R : A extends [infer P1, infer P2, infer P3] ? ((x:P2, y:P3) => R) : A extends [infer P1, infer P2, infer P3, infer P4] ? ((x:P2, y:P3, z:P4) => R) Typing Partial Invocation (Iteration 2) extend as necessary
  • 65. export function showPartialApplicationImproved() { const f1 = partial(test1); const f2 = partial(test2); const result1 = f1("abc")(123, true); const result2 = f2(123)(false, "abc"); console.log(result1); console.log(result2); } Typing Partial Invocation (Iteration 2)
  • 66. This Made Me Happy...
  • 67. – What we have been doing is ‘coding at compile time’ – We have been persuading the TypeScript compiler to create new types and make inferences for us at compile time – We have seen that we can make choices – Via the ternary conditional operator – There is no support for the procedural loops – However we can use recursion to iterate at compile time Compile Space Voodoo Pt.4 Recursive Types
  • 68. Compile Space Voodoo Pt.4 We can use recursion to iterate at compile time!!!
  • 69. type IncTable = { 0: 1; 1: 2; 2: 3; 3: 4; 4: 5; 5: 6; 6: 7; 7: 8; 8: 9; 9: 10 }; export type Inc<T extends number> = T extends keyof IncTable ? IncTable[T] : never; Recursive Types (Numbers) what do you think Inc<6> would be?
  • 70. type DecTable = { 10: 9; 9: 8; 8: 7; 7: 6; 6: 5; 5: 4; 4: 3; 3: 2; 2: 1; 1: 0 }; export type Dec<T extends number> = T extends keyof DecTable ? DecTable[T] : never; Recursive Types (Numbers) what do you think Dec<5> would be?
  • 71. export type Add<A extends number, B extends number> = { again: Add<Inc<A>, Dec<B>> return: A }[B extends 0 ? "return" : "again"] Recursive Types (Numbers) WAT? recursively increment A whilst also decrementing B until the latter is 0
  • 72. export type Add<A extends number, B extends number> = { again: Add<Inc<A>, Dec<B>> return: A }[B extends 0 ? "return" : "again"] Recursive Types (Numbers) Add<5, 3> Add<Inc<5>, Dec<3>> Add<Inc<6>, Dec<2>> Add<Inc<7>, Dec<1>> Add<Inc<8>, Dec<0>> 8
  • 73. type SampleList = [boolean, number, string]; export type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never; export type Rest<T extends any[]> = ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [] Recursive Types (Lists) use the spread operator to extract the first list item use the spread operator and function declaration syntax to extract the remainder of the list
  • 74. type SampleList = [boolean, number, string]; export type LengthByProperty<T extends any[]> = T['length'] export type LengthByRecursion<T extends any[], R extends number = 0> = { again: LengthByRecursion<Rest<T>, Inc<R>> return: R }[ T extends [] ? "return" : "again" ] Recursive Types (Lists) calculate the length of a list at compile time in two ways
  • 75. export type Prepend<E, T extends any[]> = // assign [E, ...T] to U ((head: E, ...args: T) => any) extends ((...args: infer U) => any) ? U : never //never reached Recursive Types (Lists) use the spread operator and function declaration syntax to prepend a type
  • 76. type SampleList = [boolean, number, string]; export type Reverse<T extends any[], R extends any[] = [], I extends number = 0> = { again: Reverse<T, Prepend<T[I], R>, Inc<I>> return: R }[ I extends LengthByRecursion<T> ? "return" : "again" ] Recursive Types (Lists)
  • 77. export function showRecursiveTypesWithLists() { type SampleList = [boolean, number, string]; const data1: Head<SampleList> = true; const data2: Rest<SampleList> = [12, "abc"]; const data3: LengthByRecursion<SampleList> = 3; const data4: Reverse<SampleList> = ["def", 123, false]; console.log(data1); console.log(data2); console.log(data3); console.log(data4); } Recursive Types (Lists)
  • 81. We’ve Been Coding In Type Space
  • 82. – In Test Driven Development we work from the outside in – The tests cannot write the implementation on our behalf – But they constrain our choices and point us the right way – Type Driven Development works the same way – We are not doing strong typing just to catch errors – Instead the compiler guides us to the right solution What is Type Driven Development? ...and why should you try it?