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.

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

Matthias Le Brun

February 03, 2021
Tweet

More Decks by Matthias Le Brun

Other Decks in Technology

Transcript

  1. Simplify your UI management


    with (algebraic data) types

    View Slide

  2. Matthias Le Brun


    Head of Product Design @ BeOp


    Co-founder & podcast host @ Putain de Code !


    Twitter, GitHub: @bloodyowl

    View Slide

  3. A new kind of advertising
    We're looking for a Back-End Developer (Clojure, Java, Kotlin) and a DevOps (AWS)!

    View Slide

  4. View Slide

  5. View Slide

  6. View Slide

  7. 3 things you need to deal in


    almost any UI application:


    1. Optionality


    2. Success


    3. Requests

    View Slide

  8. Optionality

    View Slide

  9. View Slide

  10. View Slide

  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

    View Slide

  12. let activitySummaryByDay: array
    undefined
    /
    /
    no activity recorded for the day


    type dayActivity = {


    calories: int,


    workoutDuration: int,


    standUpHours: int,


    }

    /
    /
    some activity

    View Slide

  13. last30daysActivity.find(item
    =
    >


    item
    =
    =
    undefined
    |
    |
    item.calories < 50


    )

    View Slide

  14. last30daysActivity.find(item
    =
    >


    item
    =
    =
    undefined
    |
    |
    item.calories < 50


    )

    /
    /
    undefined

    View Slide

  15. undefined

    View Slide

  16. undefined
    None matched


    the predicate?


    → all days
    f
    i
    lled and > 50cal
    A matching


    empty day ?


    → one day with no recorded data

    View Slide

  17. Send


    noti
    f
    i
    cation
    Do


    nothing

    View Slide

  18. let find:


    (


    array<'a>,


    'a
    =
    >
    bool


    )
    =
    >


    'a | undefined

    View Slide

  19. let find:


    (


    array<'a | undefined>,


    'a | undefined
    =
    >
    bool


    )
    =
    >


    'a | undefined | undefined

    View Slide

  20. let find:


    (


    array<'a | undefined>,


    'A | undefined
    =
    >
    bool


    )
    =
    >


    'a | undefined | undefined
    undefined

    View Slide

  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

    View Slide

  22. option<'a>

    View Slide

  23. type option<'a> =


    | Some('a)


    | None
    Option is a container, holding a value or nothing

    View Slide

  24. last30daysActivity
    -
    >
    find(item
    =
    >


    switch item {


    | None
    =
    >
    true


    | Some({calories}) when calories < 50
    =
    >
    true


    | _
    =
    >
    false


    })

    View Slide

  25. let find:


    (array<'a>, 'item
    =
    >
    bool)
    =
    >


    option<'a>

    View Slide

  26. let find:


    (array>
    >
    , 'item
    =
    >
    bool)
    =
    >


    option>
    >

    View Slide

  27. None
    /
    /
    No match


    Some(None)
    /
    /
    Found an empty day


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

    View Slide

  28. Success

    View Slide

  29. /
    /
    BE CAREFUL
    !
    !
    !

    /
    /
    Throws an error


    let f: 'param
    =
    >
    'success;

    View Slide

  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

    View Slide

  31. result<'ok, 'error>
    Result is a container, holding a value or an error

    View Slide

  32. type result<'ok, 'error> =


    | Ok('ok)


    | Error('error)

    View Slide

  33. let f: 'param
    =
    >


    result<'success, 'error>;

    View Slide

  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

    View Slide

  35. setValue(_
    =
    >
    result)

    /
    /
    then


    switch value {


    | Ok(value)
    =
    >
    /
    >


    | Error(error)
    =
    >
    /
    >


    }
    You can pass around and store the result


    for later extraction

    View Slide

  36. Requests

    View Slide

  37. View Slide

  38. type state = {


    isLoading: bool,


    error: error | null,


    data: data | null


    }

    View Slide

  39. type state = {


    isLoading: true | false,


    error: error | null,


    data: data | null


    }

    View Slide

  40. type state = {


    isLoading: true | false,


    error: error | null,


    data: data | null


    }
    2
    2
    2

    View Slide

  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

    View Slide

  42. Algebraic data types

    View Slide

  43. SUM TYPES


    A + B + C


    → OR
    PRODUCT TYPES


    A * B * C


    → AND
    Algebraic data types

    View Slide

  44. Algebraic data types
    In terms of allowed states,


    lower is often better

    View Slide

  45. type asyncData<'value> =


    | NotAsked


    | Loading


    | Done<'value>

    View Slide

  46. asyncData<


    result<'ok, 'error>


    >
    Composable!

    View Slide

  47. switch (resource) {


    | NotAsked
    =
    >
    ""


    | Loading
    =
    >
    "Loading
    .
    .
    .
    "


    | Done(Ok(value))
    =
    >
    "Loaded: "
    +
    +
    value


    | Done(Error(_))
    =
    >
    "An error occurred"


    }

    View Slide

  48. Exhaustive pattern
    matching check

    View Slide

  49. 25 ┆
    <
    /
    button>


    26 ┆
    /
    >


    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 _)


    View Slide

  50. github.com/bloodyowl/2021-01-types-talk

    View Slide

  51. Conclusion

    View Slide

  52. Types can be a great tool.

    View Slide

  53. Avoid avoidable mistakes.

    View Slide

  54. Accidental complexity ↘


    Actual business complexity ↗

    View Slide




  55. Thank you!


    Questions?
    Matthias Le Brun


    Lead front-end developer @ BeOp


    Co-founder & podcast host @ Putain de Code !


    Twitter, GitHub: @bloodyowl

    View Slide