Pro Yearly is on sale from $80 to $50! »

Simplify your UI management with (algebraic data) types

Simplify your UI management with (algebraic data) types

With higher and higher user expectations and a rapid technological evolution, the web platform complexity has exploded.
If we use JavaScript, it might be difficult to manage richer client-side features and larger codebase. With ReScript (ReasonML) concepts, we'll see with a few practical examples how algebraic data-types can help us represent more efficiently and simply the different states in our interfaces, while avoiding bugs.

Code demo: https://github.com/bloodyowl/2020-10-types-talk

37500337ba5d2aebc962959ed83928e5?s=128

Matthias Le Brun

October 15, 2020
Tweet

Transcript

  1. Simplify your UI management with (algebraic data) types

  2. Matthias Le Brun Lead front-end developer @ BeOp Co-founder &

    podcast host @ Putain de Code !
  3. Conversational units for publishers & advertisers matched against the page

    content using a semantic engine
  4. None
  5. None
  6. None
  7. None
  8. None
  9. 3 things you need to deal in almost any UI

    application: 1. Optionality 2. Success 3. Requests
  10. Optionality

  11. None
  12. None
  13. None
  14. Ring ring

  15. We want to push a notification to motivate the user

    if there's been some low or no activity day in the last month
  16. Ok

  17. let activitySummaryByDay: array<maybeActivity> undefined / / no activity recorded for

    the day type dayActivity = { calories: int, workoutDuration: int, standUpHours: int, } / / some activity
  18. last30daysActivity.find(item = > item = = undefined | | item.calories

    < 50 )
  19. last30daysActivity.find(item = > item = = undefined | | item.calories

    < 50 ) / / undefined
  20. undefined

  21. undefined Had every day activity above 50cal? Is it a

    day without any data?
  22. Send noti f i cation Do nothing

  23. let find: ( array<'item>, 'item = > bool ) =

    > 'item | undefined
  24. let find: ( array<'item | undefined>, 'item | undefined =

    > bool ) = > 'item | undefined | undefined
  25. let find: ( array<'item | undefined>, 'item | undefined =

    > bool ) = > 'item | undefined | undefined
  26. let index = last30daysActivity.findIndex(item = > item = = undefined

    | | item.calories < 50 ); if(index = = -1) { / / not found } else { let item = last30daysActivity[index]; if(item = = undefined) { / / found day without recorded activity } else { / / found day with low activity } } The "correct" way exposes implementation details
  27. option<'item>

  28. type option<'item> = | Some('item) | None Option is a

    container, holding a value or nothing
  29. let getBy: (array<'item>, 'item = > bool) = > option<'item>

  30. last30daysActivity - > Array.getBy(item = > switch item { |

    None = > true | Some({calories}) when calories < 50 = > true | _ = > false })
  31. let getBy: (array<option<'item > > , 'item = > bool)

    = > option<option<'item > >
  32. None / / No match Some(None) / / Found an

    empty day Some(Some(x)) / / Found a filled day
  33. None / / No match Some(None) / / Found an

    empty day Some(Some(x)) / / Found a filled day Do your f**king 10 000 steps
  34. let map: (option<'a>, 'a = > 'b) = > option<'b>;

    let flatMap: (option<'a>, 'a = > option<'b>) = > option<'b>; Manipulation helpers
  35. let bannerRatio = user.banner / / option<banner> - > Option.flatMap(banner

    = > banner.image) / / option<image> - > Option.map(({width, height}) = > height /. width) / / option<float> - > Option.getWithDefault(16.0 / 9.0) / / float Manipulation helpers
  36. Success

  37. / / BE CAREFUL ! ! ! / / Throws

    an error let f: 'param = > 'success;
  38. try { let x = f() handleSuccess(x) } catch(err) {

    handleFailure(err) } More codepaths, no idea where it's going to be handled or if it is
  39. promise .then( handleSuccess, handleFailure ) More codepaths, no idea where

    it's going to be handled or if it is
  40. result<'ok, 'error> Result is a container, holding a value or

    an error
  41. type result<'ok, 'error> = | Ok('ok) | Error('error)

  42. let f: 'param = > result<'success, 'error>;

  43. switch f() { | Ok(x) = > handleSuccess(x) | Error(err)

    = > handleFailure(err) } You need to handle the possibility of error if you want to extract the value
  44. Future.t<'value> Simple promises, just a potential value

  45. Future.t<result<'ok, 'error > > Composable

  46. future - > Future.get(handleResult) One path

  47. setValue(_ = > result) / / then switch value {

    | Ok(value) = > <Success value / > | Error(error) = > <ErrorMessage error / > } You can pass around and store the result for later extraction
  48. let map: (result<'a, 'b>, 'a = > 'c) = >

    result<'c, 'b>; let flatMap: (result<'a, 'b>, 'a = > result<'c, 'd>) = > result<'c, 'd>; Manipulation helpers
  49. let value = parseUserInput() / / result<int, error> - >

    Result.map(x = > x * 100) / / result<int, error> - > Result.map(Int.toString) / / result<string, error> Manipulation helpers
  50. Requests

  51. None
  52. None
  53. type state = { isLoading: bool, error: error | null,

    data: data | null }
  54. type state = { isLoading: true | false, error: error

    | null, data: data | null }
  55. type state = { isLoading: true | false, error: error

    | null, data: data | null } 2 2 2
  56. isLoading error data FALSE NULL NULL TRUE NULL NULL FALSE

    ERROR NULL TRUE TRUE NULL FALSE NULL DATA TRUE FALSE DATA FALSE TRUE DATA TRUE TRUE DATA Not asked yet Loading Done with error Done with success
  57. Sum vs Product possible states A + B + C

    = Not a lot A * B * C = A lot
  58. type requestStatus<'value> = | NotAsked | Loading | Done<'value>

  59. requestStatus< result<'ok, 'error> > Composable!

  60. switch (resource) { | NotAsked = > "" | Loading

    = > "Loading . . . " | Done(Ok(value)) = > "Loaded: " + + value | Done(Error(_)) = > "An error occurred" }
  61. Exhaustive pattern matching check

  62. 25 ┆ < / button> 26 ┆ <br / >

    27 ┆ {switch user { 28 ┆ | NotAsked | Loading = > React.null . ┆ . . . 37 ┆ | Done(Ok(None)) = > "No result was received" - > React 38 ┆ }} 39 ┆ < / > 40 ┆ } You forgot to handle a possible case here, for example: Done (Error _)
  63. Code comparison time

  64. Conclusion

  65. Types aren't necessarily boring annotations that make your life harder

    They can be a tool to help your compiler guide you and to make some bugs impossible by design
  66. Closely match natural language: easier reasoning This field HAS SOME

    user or NONE This field HAS a user or IS NULL This function RETURNS a OK value or an ERROR This function RETURNS a value or THROWS an ERROR
  67. Accidental complexity ↘ Actual business complexity ↗

  68. Accidental complexity ↘ Actual business complexity ↗

  69. Thank you! Questions? Matthias Le Brun Lead front-end developer @

    BeOp Co-founder & podcast host @ Putain de Code !