How to get away with Functional Programming in front-end applications

How to get away with Functional Programming in front-end applications

Functional programming is becoming pervasive in every branch of development, and if you're starting a new frontend project you can choose from many languages that allow you to embrace FP from the start.

However, we can rarely afford to rewrite apps from scratch. So, what to do when you want to start solving problems with functional programming in your existing JavaScript application?

In this presentation, we'll see how we can progressively migrate a codebase to TypeScript and start reaping the benefits of functional programming. You'll learn how to sneak FP into your code base, and get away with it!

C2bb0454c4af1a61e7f173d54ce17b0b?s=128

Gabriele Petronella

October 25, 2018
Tweet

Transcript

  1. Gabriele Petronella How to get away with FP in front-end

    applications Lambda World Cádiz 2018
  2. me, hi!

  3. stuff I do

  4. stuff I do

  5. What is FP?

  6. Programming with expressions

  7. Programming with expressions Statement: let permissions = []; if (user.age

    >= 18) { permissions = [generalPermissions] } else { permissions = [underagePermissions] }
  8. Programming with expressions Expression: val permissions = if (user.age >=

    18) List(generalPermission) else List(underagePermissions)
  9. Programming with expressions Composing/decomposing return users .filter(user => user.isSubscribedToNewsletter) .map(user

    => `Hi ${user.firstName}, this is our newsletter`)
  10. Programming with expressions Composing/decomposing const isSubscribed => (user: User) =>

    user.isSubscribedToNewsletter const subscribedUsers = users.filter(isSubscribed) const toNewsletterContent = user => `Hi ${user.firstName}, this is our newsletter` const newsletterContents = subscribedUsers.map(toNewsletterContent) return newsletterContents
  11. Programming with expressions Requirements 4 You can assign subexpressions to

    symbols, and replace them around 4 Function expressions don't "do" anything, only compute values 4 Function expressions use defined inputs and return the defined outputs
  12. Referential transparency

  13. Referential transparency declare function question(message: string): Promise<string> const answer1 =

    await question('What is your name?') const answer2 = await question('What is your name?') vs const p = question('What is your name?') const answer1 = await p const answer2 = await p
  14. Ok, but...

  15. ...is it useful while working on the front-end?

  16. Let's talk about "scalability"

  17. None
  18. Scales?

  19. Scaling issue #1 Fog of war

  20. Scaling issue #2 Complexity

  21. Complex vs Complicated

  22. Complicated 4 not simple 4 stll knowable 4 usually composed

    by many pieces 4 can be taken apart and studied
  23. Complicated

  24. Complex 4 not simple 4 dense interdependencies 4 not fully

    knowable 4 hard to predict accurately 4 cannot be taken apart and studied
  25. Complex

  26. Complex (even with perfect visibility)

  27. Complex Complex problems are tricker because: - their components behave

    differently depending on the global system behavior - adding/removing components affects the global system behavior
  28. Complex system, an example

  29. Complex system, another example registration.css p { font-family: 'Arial' }

  30. Complex system, another example main.css p { font-family: 'Reenie Beanie'

    !important; }
  31. Addressing scalability issues

  32. Issue 1: Fog of war

  33. Bring in the typecheckers!

  34. None
  35. None
  36. Typechecker = Visibility A typechecker will give you visibility over

    the impacts of a change, allowing you to iterate faster and with more confidence. It does not replace tests, it complements them.
  37. None
  38. Issue 2: Complexity

  39. Bring in FP!

  40. Functional Programming 4 guides us towards predictability 4 allows us

    to reason locally and ignore context
  41. In other terms...

  42. Functional Programming makes problems complicated (as opposed to complex)

  43. None
  44. Back to Earth...

  45. So, should I rewrite everything in __________ ? (Insert language

    that compiles to JS)
  46. None
  47. Add a typechecker

  48. Yeah, but which one?

  49. Flow 4 JS typechecker by Facebook 4 sophisticated type system

    with global inference 4 seems to be serving Facebook's needs
  50. TypeScript 4 Superset of Javascript by Microsoft 4 "pragmatic" type

    system with local inference 4 community oriented
  51. How?

  52. Start small Make your existing JS code build with TS

  53. TS migration vademecum 4 make your JS code work with

    a TS toolchain 4 write new features in TS 4 write definition files for your internal ecosystem 4 migrate existing JS sources to TS
  54. Make your JS code work with TS Option A Your

    project uses: - modern JavaScript - a transpiler (e.g. Babel) - a bundler (e.g. Webpack)
  55. Make your JS code work with TS (Option A) 4

    replace the transpiler with TypeScript 4 tweak tsconfig.json to adapt to your existing code { "compilerOptions": { "target": "ES5", "allowJS": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true } }
  56. Make your JS code work with TS Option B Your

    project uses: - modern JavaScript - a transpiler (e.g. Babel) - transpiler plugins (e.g. Babel plugins) - a bundler (e.g. Webpack)
  57. Make your JS code work with TS (Option B) 4

    use TypeScript for typechecking only 4 pass the typechecked code to Babel { "compilerOptions": { "target": "esnext", "allowJS": true, "allowSyntheticDefaultImports": true } }
  58. Caveat (for both options) If you're using non-standard JavaScript features,

    it's likely you will have to remove them. TypeScript implement TC39 proposals from Stage 3 on. For example, this won't be parsed: Promise.resolve(42).then(::console.log) // ^^ // bind operator
  59. Actually using types 4 Write new files 4 Change the

    file extension from .js to .ts (or .tsx for JSX files) 4 Add typings for your dependencies from Definitely Typed npm install --save-dev @types/react
  60. Writing definition files yourself Create a .d.ts file in your

    project declare module "mymodule" { export function someUtil<A>(a: A): Array<A>; }
  61. Writing definition files yourself import { someUtil } from 'mymodule';

    someUtil(2).map(x => x.length) // ^^^^^^ // error: property 'length' not found on type 'number'
  62. Can this actually be done? Any proof?

  63. Case study 4 50k+ LOC JS project 4 entirely ported

    to TS in 3 months, while actively developing features 4 internal ecosystem shared between JS and TS 4 initial port of ecosystem using definition files, then gradually rewritten
  64. What about FP? Where do I start from?

  65. 1. Stop using null and undefined

  66. 1. Stop using null and undefined interface Device { getActiveCamera():

    Camera | undefined } interface Camera { getResolution(): Resolution | undefined } interface Resolution { width: number; height: number; }
  67. 1. Stop using null and undefined JavaScript optimistic version const

    width = device.getActiveCamera().getResolution().getWidth() // do something with width
  68. 1. Stop using null and undefined let width = undefined

    const camera = device.getActiveCamera() if (camera != null) { const resolution = camera.getResolution() if (resolution != null) { width = resolution.getWidth() } } // do something with width
  69. Use Option instead device.getActiveCamera() .chain(camera => camera.getResolution()) .chain(resolution => resolution.getWidth())

    .map(width => { // do something with width }) .getOrElse(/* fallback value */)
  70. fp-ts Option and many other useful data structures are provided

    by fp-ts, a library for FP in TypeScript: https://github.com/gcanti/fp-ts (yes, it works even without HKT!)
  71. 2. Stop using Promise

  72. 2. Stop using Promise Remember this? function question(message: string): Promise<string>

    { // ... } const p = question('What is your name?') const answer1 = await p const answer2 = await p
  73. 2. Stop using Promise How about this? function question(message: string):

    Task<string> { // ... } const p = question('What is your name?') p.chain(answer1 => p.map(answer2 => /* use answer1 and answer2 */) ).run()
  74. 2. Stop using Promise Task is a referentially transparent alternative

    to Promise. Another lazy alternative to Promise is IO from funfix- effect (a TypeScript library inspired by the Scala ecosystem)
  75. 2. As a bonus, stop using async/await let result: number;

    try { result = await somePromise() } catch (e) { // handle the error result = 42 } // use 'result'
  76. 2. As a bonus, stop using async/await Use TaskEither from

    fp-ts someTaskEither() .mapLeft(e => 42) .map(result => { // use 'result' })
  77. Thank you!