Sébastien Besnier
@_sebbes_
function toLink(url){
let a = document.createElement('a');
a.href = url;
a.innerText = "Visit " + url;
body.appendChild(a);
}
function toLink(url){
let a = document.createElement('a');
a.href = url;
a.innerText = "Visit " + url;
body.appendChild(a);
}
toLink("http://hello.com");
<a href=http://hello.com>
Visit http://hello.com
</a>function toLink(url){
let a = document.createElement('a');
a.href = url;
a.innerText = "Visit " + url;
body.appendChild(a);
}
toLink("http://hello.com");
toLink({url: "http://hello.com"});<a href=http://hello.com>
Visit http://hello.com
</a><a href="[object Object]">
Visit [object Object]
</a>function toLink(url){
let a = document.createElement('a');
a.href = url;
a.innerText = "Visit " + url;
body.appendChild(a);
}
toLink("http://hello.com");
toLink({url: "http://hello.com"});<a href=http://hello.com>
Visit http://hello.com
</a><a href="[object Object]">
Visit [object Object]
</a>Runtime Issue
a_file.js ⇾ a_file.ts
(okay, there are some tricks to know, but I only have 15 minutes!)
function toLink(url: string){
let a = document.createElement('a');
a.href = url;
a.innerText = "Visit " + url;
body.appendChild(a);
}
We have the proof that "url" is a string
function toLink(url: string){
let a = document.createElement('a');
a.href = url;
a.innerText = "Visit " + url;
body.appendChild(a);
}
toLink("http://hello.com");
toLink({url: "http://hello.com"});We have the proof that "url" is a string
toLink("Hello there!");toLink("Hello there!");function toLink(url: string)function toLink(url: string)We want to prove we actually have an URL
http://hello.com/forum/searchhttp://hello.com/forum/searchPROTOCOL
DOMAIN
PATH
http://hello.com/forum/searchPROTOCOL
DOMAIN
PATH
type URL = {
protocol: "http" | "https",
domain: string,
path: Array<string>,
}http://hello.com/forum/searchPROTOCOL
DOMAIN
PATH
type URL = {
protocol: "http" | "https",
domain: string,
path: Array<string>,
}let url = {
protocol: "http",
domain: "hello.com",
path: ["forum", "search"],
}type URL = { /* ... */ };
function fromString(s: string): URL
{ /* ... */ }URL.ts
type URL = { /* ... */ };
function fromString(s: string): URL | null
{ /* ... */ }
If s is not a valid url
URL.ts
class URL {
private url: string;
private constructor(url: string){
this.url = url;
}
}class URL {
private url: string;
private constructor(url: string){
this.url = url;
}
static fromString(s: string): URL {
if (isValidUrl(s)) return new URL(s);
}
}class URL {
private url: string;
private constructor(url: string){
this.url = url;
}
static fromString(s: string): URL {
if (isValidUrl(s)) return new URL(s);
}
}class URL {
private url: string;
private constructor(url: string){
this.url = url;
}
static fromString(s: string): URL {
if (isValidUrl(s)) return new URL(s);
}
}
Proving isValidUrl
Unit tests
class URL {
private url: string;
private constructor(url: string){
this.url = url;
}
static fromString(s: string): URL {
if (isValidUrl(s)) return new URL(s);
}
}
Proving isValidUrl
Unit tests
Property based testing a.k.a Fuzzy testing (random tests)
class URL {
private url: string;
private constructor(url: string){
this.url = url;
}
static fromString(s: string): URL {
if (isValidUrl(s)) return new URL(s);
}
}class URL {
private url: string;
private constructor(url: string){
this.url = url;
}
static fromString(s: string): URL|null {
if (isValidUrl(s)) return new URL(s);
else return null;
}
}If s is not a valid url
class URL {
private url: string;
private constructor(url: string){
this.url = url;
}
static fromString(s: string): URL|null {
if (isValidUrl(s)) return new URL(s);
else return null;
}
toString(): string {
return this.url;
}
}If s is not a valid url
type URL = { /* ... */ };
function fromString(s: string): URL | null
{ /* ... */ }
function toString(u: URL ): string
{ /* ... */ }If s is not a valid url
URL.ts
type URL = { /* ... */ };
function fromString(s: string): URL | null
{ /* ... */ }
function toString(u: URL ): string
{ /* ... */ }If s is not a valid url
This function cannot "fail"
URL.ts
type URL = { /* ... */ };
function fromString(s: string): URL | null
{ /* ... */ }
function toString(u: URL ): string
{ /* ... */ }If s is not a valid url
This function cannot "fail"
URL.ts
Make fromString the only way to create URL values:
function toLink(url: URL){
const a = document.createElement('a');
const urlStr = url.toString();
a.href = urlStr;
a.innerText = "Visit " + urlStr;
body.appendChild(a);
}
We have now the proof that "url" is an actual URL
const userInput = prompt("Enter an URL");
const url = URL.fromString(userInput);
toLink(url);
const userInput = prompt("Enter an URL");
const url = URL.fromString(userInput);
toLink(url);
const userInput = prompt("Enter an URL");
const url = URL.fromString(userInput);
toLink(url);
Turn on the
--strictNullChecks
TS option
const userInput = prompt("Enter an URL");
const url = URL.fromString(userInput);
toLink(url);
if(url === null) {
alert("Not an url!");
} else {
toLink(url);
}Turn on the
--strictNullChecks
TS option
function toLink(url: URL)function toLink(url: URL): voidfunction toLink(url: URL): HTMLElement
function toLink(url: URL): Promise<Person>function toLink(url: URL): HTMLElement
function toLink(url: URL): Promise<Person>Return value gives the proof of what it performs
LE FIGORA
TypeScript: prouvez votre code!
PUB: Buvez
Caco Calo
DECOUVREZ NOS PRODUITS
CACO CALO
http://figora.com
http://caco-calo.com
LE FIGORA
TypeScript: prouvez votre code!
PUB: Buvez
Caco Calo
DECOUVREZ NOS PRODUITS
CACO CALO
http://figora.com
http://caco-calo.com
AD SERVER
http://ad.com/caco?token=figora
LE FIGORA
TypeScript: prouvez votre code!
PUB: Buvez
Caco Calo
DECOUVREZ NOS PRODUITS
CACO CALO
http://figora.com
http://caco-calo.com
AD SERVER
http://ad.com/caco?token=figora
http://ad.com/caco?token=figora
http://ad.com/caco?token=figora
type Campaign = {
// ...
trackingUrl: Url,
// ...
}type Publisher = {
// ...
token: Token,
// ...
}Loaded independently at start-up
type Campaign = {
// ...
trackingUrl: Url,
// ...
}type Publisher = {
// ...
token: Token,
// ...
}function displayAd() {
toLink(
store.trackingUrl
.addParams(
"token",
store.token.toString()
))
}type Campaign = {
// ...
trackingUrl: Url,
// ...
}type Publisher = {
// ...
token: Token,
// ...
}How to ensure:
function displayAd() {
toLink(
store.trackingUrl
.addParams(
"token",
store.token.toString()
))
}type Campaign = {
// ...
trackingUrl: Url,
// ...
}type Publisher = {
// ...
token: Token,
// ...
}function displayAd() {
toLink(
store.trackingUrl
.addParams(
"token",
store.token.toString()
))
}How to ensure:
type Campaign = {
// ...
trackingUrl:
ConfigurableUrl,
// ...
}type Publisher = {
// ...
token: Token,
// ...
}type Campaign = {
// ...
trackingUrl:
ConfigurableUrl,
// ...
}type Publisher = {
// ...
token: Token,
// ...
}Compile error
function displayAd() {
toLink(store.trackingUrl);
}class ConfigurableUrl {
private url: Url;
contructor(url: Url) {
this.url = url;
}
configure(token: Token): Url {
return this.url.addParam(
"token",
token.toString()
)
}
}class ConfigurableUrl {
private url: Url;
contructor(url: Url) {
this.url = url;
}
configure(token: Token): Url {
return this.url.addParam(
"token",
token.toString()
)
}
}The only way to get an Url back
class ConfigurableUrl {
private url: Url;
contructor(url: Url) {
this.url = url;
}
configure(token: Token): Url {
return this.url.addParam(
"token",
token.toString()
)
}
}type Campaign = {
// ...
trackingUrl:
ConfigurableUrl,
// ...
}On loading
class ConfigurableUrl {
private url: Url;
contructor(url: Url) {
this.url = url;
}
configure(token: Token): Url {
return this.url.addParam(
"token",
token.toString()
)
}
}type Campaign = {
// ...
trackingUrl:
ConfigurableUrl,
// ...
}On loading
function displayAd() {
toLink(store.trackingUrl
.configure(store.token)
);
}On display
Very high
ratio
GUARANTEES
EFFORT
type Campaign = {
// ...
theme:
Array<string>,
trackingUrl:
string,
counter: number,
// ...
}Level 1
type Campaign = {
// ...
theme:
Array<string>,
trackingUrl:
string,
counter: number,
// ...
}type Campaign = {
// ...
theme:
Array<Color>,
trackingUrl:
ConfigurableUrl,
counter: Counter,
// ...
}Level 1
Level 2
TypeScript can bite you!
You can cheat on type signatures
But, hey! my 10 minutes are almost (already?) gone!
Elm
ReScript
PureScript
Sébastien Besnier
@_sebbes_