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/search
http://hello.com/forum/search
PROTOCOL
DOMAIN
PATH
http://hello.com/forum/search
PROTOCOL
DOMAIN
PATH
type URL = {
protocol: "http" | "https",
domain: string,
path: Array<string>,
}
http://hello.com/forum/search
PROTOCOL
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): void
function 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_