Save 37% off PRO during our Black Friday Sale! »

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.

https://github.com/bloodyowl/2021-01-types-talk

37500337ba5d2aebc962959ed83928e5?s=128

Matthias Le Brun

February 03, 2021
Tweet

Transcript

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

  2. Matthias Le Brun Head of Product Design @ BeOp Co-founder

    & podcast host @ Putain de Code ! Twitter, GitHub: @bloodyowl
  3. A new kind of advertising We're looking for a Back-End

    Developer (Clojure, Java, Kotlin) and a DevOps (AWS)!
  4. None
  5. None
  6. None
  7. 3 things you need to deal in almost any UI

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

  9. None
  10. None
  11. «We want to push a notification to motivate the user

    if they have some days with low or no activity in the last 30 days» — the product team
  12. let activitySummaryByDay: array<maybeActivity> undefined / / no activity recorded for

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

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

    < 50 ) / / undefined
  15. undefined

  16. undefined None matched the predicate? → all days f i

    lled and > 50cal A matching empty day ? → one day with no recorded data
  17. Send noti f i cation Do nothing

  18. let find: ( array<'a>, 'a = > bool ) =

    > 'a | undefined
  19. let find: ( array<'a | undefined>, 'a | undefined =

    > bool ) = > 'a | undefined | undefined
  20. let find: ( array<'a | undefined>, 'A | undefined =

    > bool ) = > 'a | undefined | undefined undefined
  21. 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
  22. option<'a>

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

    container, holding a value or nothing
  24. last30daysActivity - > find(item = > switch item { |

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

  26. let find: (array<option<'a > > , 'item = > bool)

    = > option<option<'a > >
  27. None / / No match Some(None) / / Found an

    empty day Some(Some(x)) / / Found a filled day Do your f**king 10 000 steps
  28. Success

  29. / / BE CAREFUL ! ! ! / / Throws

    an error let f: 'param = > 'success;
  30. 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
  31. result<'ok, 'error> Result is a container, holding a value or

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

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

  34. 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
  35. setValue(_ = > result) / / then switch value {

    | Ok(value) = > <Success value / > | Error(error) = > <ErrorMessage error / > } You can pass around and store the result for later extraction
  36. Requests

  37. None
  38. type state = { isLoading: bool, error: error | null,

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

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

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

    ERROR NULL FALSE NULL DATA TRUE TRUE NULL TRUE FALSE DATA FALSE TRUE DATA TRUE TRUE DATA Not asked yet Loading Done with error Done with success
  42. Algebraic data types

  43. SUM TYPES A + B + C → OR PRODUCT

    TYPES A * B * C → AND Algebraic data types
  44. Algebraic data types In terms of allowed states, lower is

    often better
  45. type asyncData<'value> = | NotAsked | Loading | Done<'value>

  46. asyncData< result<'ok, 'error> > Composable!

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

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

  49. 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 _)
  50. github.com/bloodyowl/2021-01-types-talk

  51. Conclusion

  52. Types can be a great tool.

  53. Avoid avoidable mistakes.

  54. Accidental complexity ↘ Actual business complexity ↗

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

    BeOp Co-founder & podcast host @ Putain de Code ! Twitter, GitHub: @bloodyowl