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

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

Matthias Le Brun

October 15, 2020
Tweet

More Decks by Matthias Le Brun

Other Decks in Technology

Transcript

  1. 3 things you need to deal in almost any UI

    application: 1. Optionality 2. Success 3. Requests
  2. We want to push a notification to motivate the user

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

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

    the day type dayActivity = { calories: int, workoutDuration: int, standUpHours: int, } / / some activity
  5. let find: ( array<'item | undefined>, 'item | undefined =

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

    > bool ) = > 'item | undefined | undefined
  7. 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
  8. type option<'item> = | Some('item) | None Option is a

    container, holding a value or nothing
  9. last30daysActivity - > Array.getBy(item = > switch item { |

    None = > true | Some({calories}) when calories < 50 = > true | _ = > false })
  10. None / / No match Some(None) / / Found an

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

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

    let flatMap: (option<'a>, 'a = > option<'b>) = > option<'b>; Manipulation helpers
  13. 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
  14. / / BE CAREFUL ! ! ! / / Throws

    an error let f: 'param = > 'success;
  15. 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
  16. 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
  17. setValue(_ = > result) / / then switch value {

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

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

    Result.map(x = > x * 100) / / result<int, error> - > Result.map(Int.toString) / / result<string, error> Manipulation helpers
  20. type state = { isLoading: true | false, error: error

    | null, data: data | null } 2 2 2
  21. 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
  22. Sum vs Product possible states A + B + C

    = Not a lot A * B * C = A lot
  23. switch (resource) { | NotAsked = > "" | Loading

    = > "Loading . . . " | Done(Ok(value)) = > "Loaded: " + + value | Done(Error(_)) = > "An error occurred" }
  24. 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 _)
  25. 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
  26. 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
  27. Thank you! Questions? Matthias Le Brun Lead front-end developer @

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