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

Functional Programming in Front-end Applications

Functional Programming in Front-end Applications

Gabriele Petronella

March 12, 2019
Tweet

More Decks by Gabriele Petronella

Other Decks in Programming

Transcript

  1. Gabriele Petronella
    Functional Programming in
    front-end applications
    FpInBo. 12/03/2019

    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:
    4 their components behave differently depending on
    the global system behavior
    4 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. TypeScript
    4 Superset of Javascript by Microsoft
    4 "pragmatic" type system with local inference
    4 "progressive" type system (can have untyped parts)
    4 community oriented

    View Slide

  49. How?

    View Slide

  50. 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

  51. Caveat
    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

  52. 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

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

    View Slide

  54. 1. Stop using null and
    undefined

    View Slide

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

    View Slide

  56. 1. Stop using null and undefined
    const width = device.getActiveCamera().getResolution().width;
    if (width != null) {
    return 'No width :(';
    }
    return `Width is ${width}';

    View Slide

  57. 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.width;
    }
    }
    if (width != null) {
    return 'No width :(';
    }
    return `Width is ${width}';

    View Slide

  58. Welcome fp-ts
    fp-ts is a library for typed
    functional programming in
    TypeScript
    https://github.com/gcanti/fp-ts

    View Slide

  59. Welcome Option
    Option is a container for an optional value of type A.
    4 If the value is there, we have an instance of Some.
    4 If the value is not there, we have an instance of None.

    View Slide

  60. Example
    import { Option, some, none } from "fp-ts/lib/Option";
    function head(arr: Array): Option {
    return arr.length > 0 ? some(arr[0]) : none;
    }
    head([]); // none
    head([1, 2]); // some(1)

    View Slide

  61. How to work with Option
    map
    head([1, 2]).map(n => n + 100); // some(101)

    View Slide

  62. How to work with Option
    nested map
    function doSomething(n: number): Option {
    return number > 1 ? some(n.toString()) : none;
    }
    head([2, 3]).map(n => doSomething(n)); // some(some("2"))
    head([1, 2]).map(n => doSomething(n)); // some(none)

    View Slide

  63. chain
    function doSomething(n: number): Option {
    return number > 1 ? some(n.toString()) : none;
    }
    head([2, 3]).chain(n => doSomething(n)); // some("2")
    head([1, 2]).chain(n => doSomething(n)); // none
    head([]).chain(n => doSomething(n)); // none

    View Slide

  64. getOrElse
    head([42, 43]).getOrElse(-1); // 42
    head([]).getOrElse(-1); // -1

    View Slide

  65. Let's try
    interface Device {
    getActiveCamera(): Option;
    }
    interface Camera {
    getResolution(): Option;
    }
    interface Resolution {
    width: number;
    height: number;
    }

    View Slide

  66. Using Option
    device
    .getActiveCamera()
    .chain(camera => camera.getResolution())
    .map(resolution => `Width is ${resolution.width}`)
    .getOrElse("No width :(");

    View Slide

  67. It's not me, it's them!
    import { fromNullable } from 'fp-ts/lib/Option'
    function safeFind(arr: Array, f: A => Boolean): Option {
    return fromNullable(arr.find(f))
    }
    [1, 2, 3].find(x => x < 3) // 2
    [1, 2, 3].find(x => x < 0) // undefined
    safeFind([1, 2, 3], x => x < 3) // some(2)
    safeFind([1, 2, 3], x => x < 0) // none

    View Slide

  68. In real life
    import { array } from "fp-ts/lib/Array";
    array.find([1, 2, 3], x => x < 3); // some(2)
    array.find([1, 2, 3], x => x < 0); // none

    View Slide

  69. 2. Stop throwing
    exceptions

    View Slide

  70. 2. Stop throwing exceptions
    function validateUser(user: User): User {
    if (user.age >= 18) {
    return user;
    } else {
    throw "User is underage!";
    }
    }
    validateUser(underageUser).name; // boom

    View Slide

  71. Welcome Either
    Remember Option? Either is a more powerful
    alternative:
    4 when the value is there: Right
    4 when the value is not there: Left

    View Slide

  72. import { Either, right, left } from "fp-ts/lib/Either";
    function validateUser(user: User): Either {
    if (user.age >= 18) {
    return right(user);
    } else {
    return left("User is underage!");
    }
    }
    // compile error: 'name' is not a member of 'Either'
    validateUser(underageUser).name;

    View Slide

  73. Using Either
    Familiar?
    declare function doSomething(u: User): Either;
    validateUser(user).map(u => u.name);
    validateUser(user).mapLeft(e => `Error was: ${e}`);
    validateUser(user).chain(user => doSomething(user));
    validateUser(user).getOrElse(defaultUser);

    View Slide

  74. It's not me, it's them!
    import { tryCatch } from "fp-ts/lib/Either";
    function safeParseJson(raw: string): Either {
    return tryCatch(() => JSON.parse(raw)).mapLeft(e => e.message);
    }
    JSON.parse("{}"); // {}
    JSON.parse("{"); //
    !
    SyntaxError: Unexpected end of JSON input
    safeParseJson("{}"); // right({})
    safeParseJson("{"); // left("Unexpected end of JSON input")

    View Slide

  75. 3. Stop using Promise

    View Slide

  76. 3. 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

  77. Welcome Task
    Task is like Promise but it's lazy and referentially
    transparent.

    View Slide

  78. Example
    declare function question(message: string): Task;
    const q = question("What is your name?");
    const t = q.chain(answer1 =>
    q.map(answer2 => [answer1, answer2])
    ); // Task>
    //////////////// nothing is executing yet ////////////////
    t.run(); // Promise>

    View Slide

  79. What about (typed)
    errors?

    View Slide

  80. Welcome TaskEither
    TaskEither mixes the capabilities of Task and Either
    You could use Task> but TaskEither has a
    more convenient API.

    View Slide

  81. Example
    import {
    TaskEither, tryCatch, taskEither, fromLeft
    } from "fp-ts/lib/TaskEither";
    function validate(user: User): TaskEither {
    tryCatch(() => fetch("/myApi/validateUser", {
    method: "POST", body: JSON.stringify(user)
    })
    ).chain(response => {
    return reponse.ok ?
    taskEither.of(user) :
    fromLeft("User is invalid");
    });
    }

    View Slide

  82. Control Flow: vanilla vs FP
    vanilla FP
    optionality typeof x != null / && map/chain
    errors
    try catch map/chain
    async (Promise) then / catch map/chain
    async (async/await)
    try catch map/chain
    ...
    whatever else something ad-hoc map/chain

    View Slide

  83. Thank you!

    View Slide

  84. Resources
    4 https://gcanti.github.io/fp-ts/
    4 https://github.com/MostlyAdequate/mostly-
    adequate-guide
    4 http://italiajs.herokuapp.com/
    4 channels #fp and #typescript

    View Slide

  85. questions.d.ts
    @gabro27

    View Slide