Functional Programming in Front-end Applications

Functional Programming in Front-end Applications

C2bb0454c4af1a61e7f173d54ce17b0b?s=128

Gabriele Petronella

March 12, 2019
Tweet

Transcript

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

  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: 4 their components behave

    differently depending on the global system behavior 4 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. 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
  49. How?

  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
  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
  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
  53. What about FP? Where do I start from?

  54. 1. Stop using null and undefined

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

    Camera | undefined; } interface Camera { getResolution(): Resolution | undefined; } interface Resolution { width: number; height: number; }
  56. 1. Stop using null and undefined const width = device.getActiveCamera().getResolution().width;

    if (width != null) { return 'No width :('; } return `Width is ${width}';
  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}';
  58. Welcome fp-ts fp-ts is a library for typed functional programming

    in TypeScript https://github.com/gcanti/fp-ts
  59. Welcome Option Option<A> is a container for an optional value

    of type A. 4 If the value is there, we have an instance of Some<A>. 4 If the value is not there, we have an instance of None.
  60. Example import { Option, some, none } from "fp-ts/lib/Option"; function

    head<A>(arr: Array<A>): Option<A> { return arr.length > 0 ? some(arr[0]) : none; } head([]); // none head([1, 2]); // some(1)
  61. How to work with Option map head([1, 2]).map(n => n

    + 100); // some(101)
  62. How to work with Option nested map function doSomething(n: number):

    Option<A> { 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)
  63. chain function doSomething(n: number): Option<A> { 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
  64. getOrElse head([42, 43]).getOrElse(-1); // 42 head([]).getOrElse(-1); // -1

  65. Let's try interface Device { getActiveCamera(): Option<Camera>; } interface Camera

    { getResolution(): Option<Resolution>; } interface Resolution { width: number; height: number; }
  66. Using Option device .getActiveCamera() .chain(camera => camera.getResolution()) .map(resolution => `Width

    is ${resolution.width}`) .getOrElse("No width :(");
  67. It's not me, it's them! import { fromNullable } from

    'fp-ts/lib/Option' function safeFind<A>(arr: Array<A>, f: A => Boolean): Option<A> { 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
  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
  69. 2. Stop throwing exceptions

  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
  71. Welcome Either Remember Option<A>? Either<L, R> is a more powerful

    alternative: 4 when the value is there: Right<R> 4 when the value is not there: Left<L>
  72. import { Either, right, left } from "fp-ts/lib/Either"; function validateUser(user:

    User): Either<string, User> { if (user.age >= 18) { return right(user); } else { return left("User is underage!"); } } // compile error: 'name' is not a member of 'Either<string, User>' validateUser(underageUser).name;
  73. Using Either Familiar? declare function doSomething(u: User): Either<string, User>; validateUser(user).map(u

    => u.name); validateUser(user).mapLeft(e => `Error was: ${e}`); validateUser(user).chain(user => doSomething(user)); validateUser(user).getOrElse(defaultUser);
  74. It's not me, it's them! import { tryCatch } from

    "fp-ts/lib/Either"; function safeParseJson(raw: string): Either<string, unknown> { 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")
  75. 3. Stop using Promise

  76. 3. 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;
  77. Welcome Task Task<A> is like Promise<A> but it's lazy and

    referentially transparent.
  78. Example declare function question(message: string): Task<string>; const q = question("What

    is your name?"); const t = q.chain(answer1 => q.map(answer2 => [answer1, answer2]) ); // Task<Array<string>> //////////////// nothing is executing yet //////////////// t.run(); // Promise<Array<string>>
  79. What about (typed) errors?

  80. Welcome TaskEither TaskEither mixes the capabilities of Task and Either

    You could use Task<Either<L, A>> but TaskEither has a more convenient API.
  81. Example import { TaskEither, tryCatch, taskEither, fromLeft } from "fp-ts/lib/TaskEither";

    function validate(user: User): TaskEither<string, User> { tryCatch(() => fetch("/myApi/validateUser", { method: "POST", body: JSON.stringify(user) }) ).chain(response => { return reponse.ok ? taskEither.of(user) : fromLeft("User is invalid"); }); }
  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
  83. Thank you!

  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
  85. questions.d.ts @gabro27