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

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!

Gabriele Petronella

October 25, 2018
Tweet

More Decks by Gabriele Petronella

Other Decks in Programming

Transcript

  1. Gabriele Petronella
    How to get away with FP
    in front-end applications
    Lambda World Cádiz 2018

    View Slide

  2. me, hi!

    View Slide

  3. stuff I do

    View Slide

  4. stuff I do

    View Slide

  5. What is FP?

    View Slide

  6. Programming with
    expressions

    View Slide

  7. Programming with expressions
    Statement:
    let permissions = [];
    if (user.age >= 18) {
    permissions = [generalPermissions]
    } else {
    permissions = [underagePermissions]
    }

    View Slide

  8. Programming with expressions
    Expression:
    val permissions =
    if (user.age >= 18)
    List(generalPermission)
    else
    List(underagePermissions)

    View Slide

  9. Programming with expressions
    Composing/decomposing
    return users
    .filter(user => user.isSubscribedToNewsletter)
    .map(user => `Hi ${user.firstName}, this is our newsletter`)

    View Slide

  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

    View Slide

  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

    View Slide

  12. Referential transparency

    View Slide

  13. Referential transparency
    declare function question(message: string): Promise
    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

    View Slide

  14. Ok, but...

    View Slide

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

    View Slide

  16. Let's talk about
    "scalability"

    View Slide

  17. View Slide

  18. Scales?

    View Slide

  19. Scaling issue #1
    Fog of war

    View Slide

  20. Scaling issue #2
    Complexity

    View Slide

  21. Complex vs Complicated

    View Slide

  22. Complicated
    4 not simple
    4 stll knowable
    4 usually composed by many pieces
    4 can be taken apart and studied

    View Slide

  23. Complicated

    View Slide

  24. Complex
    4 not simple
    4 dense interdependencies
    4 not fully knowable
    4 hard to predict accurately
    4 cannot be taken apart and studied

    View Slide

  25. Complex

    View Slide

  26. Complex (even with perfect visibility)

    View Slide

  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

    View Slide

  28. Complex system, an example

    View Slide

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

    View Slide

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

    View Slide

  31. Addressing scalability
    issues

    View Slide

  32. Issue 1: Fog of war

    View Slide

  33. Bring in the typecheckers!

    View Slide

  34. View Slide

  35. View Slide

  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.

    View Slide

  37. View Slide

  38. Issue 2: Complexity

    View Slide

  39. Bring in FP!

    View Slide

  40. Functional Programming
    4 guides us towards predictability
    4 allows us to reason locally and ignore context

    View Slide

  41. In other terms...

    View Slide

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

    View Slide

  43. View Slide

  44. Back to Earth...

    View Slide

  45. So, should I rewrite
    everything in
    __________ ?
    (Insert language that compiles to JS)

    View Slide

  46. View Slide

  47. Add a typechecker

    View Slide

  48. Yeah, but which one?

    View Slide

  49. Flow
    4 JS typechecker by Facebook
    4 sophisticated type system with global inference
    4 seems to be serving Facebook's needs

    View Slide

  50. TypeScript
    4 Superset of Javascript by Microsoft
    4 "pragmatic" type system with local inference
    4 community oriented

    View Slide

  51. How?

    View Slide

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

    View Slide

  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

    View Slide

  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)

    View Slide

  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
    }
    }

    View Slide

  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)

    View Slide

  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
    }
    }

    View Slide

  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

    View Slide

  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

    View Slide

  60. Writing definition files yourself
    Create a .d.ts file in your project
    declare module "mymodule" {
    export function someUtil(a: A): Array;
    }

    View Slide

  61. Writing definition files yourself
    import { someUtil } from 'mymodule';
    someUtil(2).map(x => x.length)
    // ^^^^^^
    // error: property 'length' not found on type 'number'

    View Slide

  62. Can this actually be done?
    Any proof?

    View Slide

  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

    View Slide

  64. What about FP?
    Where do I start from?

    View Slide

  65. 1. Stop using null and
    undefined

    View Slide

  66. 1. Stop using null and undefined
    interface Device {
    getActiveCamera(): Camera | undefined
    }
    interface Camera {
    getResolution(): Resolution | undefined
    }
    interface Resolution {
    width: number;
    height: number;
    }

    View Slide

  67. 1. Stop using null and undefined
    JavaScript optimistic version
    const width = device.getActiveCamera().getResolution().getWidth()
    // do something with width

    View Slide

  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

    View Slide

  69. Use Option instead
    device.getActiveCamera()
    .chain(camera => camera.getResolution())
    .chain(resolution => resolution.getWidth())
    .map(width => {
    // do something with width
    })
    .getOrElse(/* fallback value */)

    View Slide

  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!)

    View Slide

  71. 2. Stop using Promise

    View Slide

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

    View Slide

  73. 2. Stop using Promise
    How about this?
    function question(message: string): Task {
    // ...
    }
    const p = question('What is your name?')
    p.chain(answer1 =>
    p.map(answer2 => /* use answer1 and answer2 */)
    ).run()

    View Slide

  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)

    View Slide

  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'

    View Slide

  76. 2. As a bonus, stop using async/await
    Use TaskEither from fp-ts
    someTaskEither()
    .mapLeft(e => 42)
    .map(result => {
    // use 'result'
    })

    View Slide

  77. Thank you!

    View Slide