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 full-size slide

  2. Programming with
    expressions

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  6. 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 full-size slide

  7. 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 full-size slide

  8. Referential transparency

    View full-size slide

  9. 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 full-size slide

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

    View full-size slide

  11. Let's talk about
    "scalability"

    View full-size slide

  12. Scaling issue #1
    Fog of war

    View full-size slide

  13. Scaling issue #2
    Complexity

    View full-size slide

  14. Complex vs Complicated

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  17. Complex (even with perfect visibility)

    View full-size slide

  18. 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 full-size slide

  19. Complex system, an example

    View full-size slide

  20. Complex system,
    another example
    registration.css
    p {
    font-family: "Arial";
    }

    View full-size slide

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

    View full-size slide

  22. Addressing scalability
    issues

    View full-size slide

  23. Issue 1: Fog of war

    View full-size slide

  24. Bring in the typecheckers!

    View full-size slide

  25. 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 full-size slide

  26. Issue 2: Complexity

    View full-size slide

  27. Bring in FP!

    View full-size slide

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

    View full-size slide

  29. In other terms...

    View full-size slide

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

    View full-size slide

  31. Back to Earth...

    View full-size slide

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

    View full-size slide

  33. Add a typechecker

    View full-size slide

  34. 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 full-size slide

  35. 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 full-size slide

  36. 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 full-size slide

  37. 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 full-size slide

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

    View full-size slide

  39. 1. Stop using null and
    undefined

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  42. 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 full-size slide

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

    View full-size slide

  44. 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 full-size slide

  45. 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 full-size slide

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

    View full-size slide

  47. 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 full-size slide

  48. 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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

  54. 2. Stop throwing
    exceptions

    View full-size slide

  55. 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 full-size slide

  56. 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 full-size slide

  57. 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 full-size slide

  58. 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 full-size slide

  59. 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 full-size slide

  60. 3. Stop using Promise

    View full-size slide

  61. 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 full-size slide

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

    View full-size slide

  63. 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 full-size slide

  64. What about (typed)
    errors?

    View full-size slide

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

    View full-size slide

  66. 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 full-size slide

  67. 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 full-size slide

  68. 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 full-size slide

  69. questions.d.ts
    @gabro27

    View full-size slide