Slide 1

Slide 1 text

Type Your Business Logic with Flow CT Wu Front-End Developers Taiwan 2018.11 wu_ct wuct

Slide 2

Slide 2 text

• Building from scratch is not the hardest part of programming but changing legacy code. • Especially when all authors are now ex-colleagues and there is no document. • Programmers are afraid to change legacy code because they do not know what will they break. • Testing can not save us, because "Testing shows the presence, not the absence of bugs.”

Slide 3

Slide 3 text

Scenario 1: refactor a function • There is a widely used function in a huge code base, e.g.
 const sendMailTo = (account: string) => {}; • For some reasons, we need to make it receiving an email rather than an account:
 const sendMailTo = (email: string) => {}; • Here are some options: 1. Build a new function and keep the legacy one. 2. Change the current function and all callers, then hope tests can save us. 3. Let the type system help us!

Slide 4

Slide 4 text

Scenario 1: how to do this? // @flow // email.js // Now export const sendMailTo = (account: string) => {}; // What we want export const sendMailTo = (email: string) => {}; // @flow // app.js import { sendMailTo } from './email'; sendMailTo('wuct');

Slide 5

Slide 5 text

Type aliases can not save us // @flow // email.js type Email = string; export const sendMailTo = (email: Email) => {}; // @flow // app.js import { sendMailTo } from './email'; sendMailTo('wuct'); // still compiled

Slide 6

Slide 6 text

We need opaque type aliases // @flow // email.js opaque type Email = string; export const sendMailTo = (email: Email) => {}; // @flow // app.js import { sendMailTo } from './email'; sendMailTo('wat'); // failed

Slide 7

Slide 7 text

Modularization // @flow // email.js import isEmail from './isEmail'; opaque type Email = string; export const createEmail = (s: string): ?Email => { return isEmail(s) ? s : null; }; export const sendMailTo = (email: Email) => {}; // @flow // app.js import { sendMailTo, createEmail } from './email'; const email = createEmail('wat'); if (email) { sendMailTo(email); // compiled }

Slide 8

Slide 8 text

More complex logic: is this email unique? // @flow // email.js import isEmail from './isEmail'; opaque type Email = string; export const createEmail = (s: string): ?Email => { return isEmail(s) ? s : null; }; export const isUnique = (email: Email): boolean => { // some heavy computation return email.startsWith('x'); }; export const signUp = (email: Email): boolean => { return isUnique(email); }; // @flow // app.js import { createEmail, isUnique, signUp } from './ email'; const email = createEmail('[email protected]'); // For better UX, we might want to check an email // is unique before submitting if (email && !isUnique(email)) { alert('The email is duplicated.'); } // Another part of our code base handling submitting if (email) { signUp(email); } // The isUnique is invoked twice for the same email. // Can we avoid it with modularization in mind?

Slide 9

Slide 9 text

Using phantom types // @flow // email.js import isEmail from './isEmail'; opaque type Email = string; class IsNotChecked {} class IsUnique {} export const createEmail = (s: string): ?Email => { return isEmail(s) ? s : null; }; export const isUnique = (email: Email): ? Email => { // some heavy computation return email.startsWith('x') ? email : null; }; export const signUp = (email: Email): boolean => { return true; }; // @flow // app.js import { createEmail, isUnique, signUp } from './ email'; const email = createEmail('[email protected]'); if (email && !isUnique(email)) { alert('The email is duplicated.'); } // Another part of our code base handling submitting if (email) { signUp(email); // failed }

Slide 10

Slide 10 text

Using phantom types (compiled) // @flow // email.js import isEmail from './isEmail'; opaque type Email = string; class IsNotChecked {} class IsUnique {} export const createEmail = (s: string): ?Email => { return isEmail(s) ? s : null; }; export const isUnique = (email: Email): ? Email => { // some heavy computation return email.startsWith('x') ? email : null; }; export const signUp = (email: Email): boolean => { return true; }; // @flow // app.js import { createEmail, isUnique, signUp } from './ email'; const email = createEmail('[email protected]'); const uniqueEmail = email ? isUnique(email) : null; if (!uniqueEmail) { alert('The email is duplicated or the format is wrong.'); } // Another part of our code base handling submitting if (uniqueEmail) { signUp(uniqueEmail); // compiled }

Slide 11

Slide 11 text

More example: vehicle.js // @flow type Vehicle = 'car' | 'bus' | 'bike'; export const wheels = (vehicle: Vehicle) => { return vehicle === 'bike' ? 2 : 4; }; export const fuel = (vehicle: Vehicle) => { if (vehicle === 'bike') throw 'error'; // fuel the vehicle }; // @flow // index.js import { wheels, fuel } from './vehicle'; const car = 'car'; wheels(car); fuel(car); const bike = 'bike'; wheels(bike); fuel(bike); // runtime error // can we do better?

Slide 12

Slide 12 text

Example: vehicle.js with phantom types // @flow class Petrol {} class Pedal {} type PetrolVehicle = 'car' | 'bus'; type PedalVehicle = 'bike'; opaque type Vehicle

= PetrolVehicle | PedalVehicle; export const createPetrolVehicle = (t: PetrolVehicle): Vehicle => { return t; }; export const createPedalVehicle = (t: PedalVehicle): Vehicle => { return t; }; export const wheels =

(vehicle: Vehicle

) => { return vehicle === 'bike' ? 2 : 4; }; export const fuel = (vehicle: Vehicle) => { // only a vehicle powered by petrol can be fueled }; // @flow // index.js import { createPetrolVehicle, createPedalVehicle, wheels, fuel, } from './vehicle'; const car = createPetrolVehicle('car'); wheels(car); fuel(car); const bike = createPedalVehicle('bike'); wheels(bike); fuel(bike); // compile-time error

Slide 13

Slide 13 text

Finite State Machines Exercise: implement this logic with types

Slide 14

Slide 14 text

Any question? wu_ct wuct