Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Type Your Business Logic with Flow

672ba495b3eadfdb0daee60ed354bdff?s=47 wuct
November 28, 2018

Type Your Business Logic with Flow

672ba495b3eadfdb0daee60ed354bdff?s=128

wuct

November 28, 2018
Tweet

Transcript

  1. Type Your Business Logic with Flow CT Wu Front-End Developers

    Taiwan 2018.11 wu_ct wuct
  2. • 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.”
  3. 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!
  4. 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');
  5. 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
  6. 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
  7. 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 }
  8. 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('wat@example.com'); // 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?
  9. Using phantom types // @flow // email.js import isEmail from

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

    from './isEmail'; opaque type Email<T> = string; class IsNotChecked {} class IsUnique {} export const createEmail = (s: string): ?Email<IsNotChecked> => { return isEmail(s) ? s : null; }; export const isUnique = (email: Email<IsNotChecked>): ? Email<IsUnique> => { // some heavy computation return email.startsWith('x') ? email : null; }; export const signUp = (email: Email<IsUnique>): boolean => { return true; }; // @flow // app.js import { createEmail, isUnique, signUp } from './ email'; const email = createEmail('wat@example.com'); 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 }
  11. 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?
  12. Example: vehicle.js with phantom types // @flow class Petrol {}

    class Pedal {} type PetrolVehicle = 'car' | 'bus'; type PedalVehicle = 'bike'; opaque type Vehicle<P> = PetrolVehicle | PedalVehicle; export const createPetrolVehicle = (t: PetrolVehicle): Vehicle<Petrol> => { return t; }; export const createPedalVehicle = (t: PedalVehicle): Vehicle<Pedal> => { return t; }; export const wheels = <P>(vehicle: Vehicle<P>) => { return vehicle === 'bike' ? 2 : 4; }; export const fuel = (vehicle: Vehicle<Petrol>) => { // 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
  13. Finite State Machines Exercise: implement this logic with types

  14. Any question? wu_ct wuct