$30 off During Our Annual Pro Sale. View Details »

Making Unreasonable States Impossible

Making Unreasonable States Impossible

This talk goes deeper into the world of variant types and pattern matching and puts them into a practical context. You will learn how these tools help you design solid APIs, which are impossible to misuse by consumers. Additionally you will get more insights into practical ReasonReact code.

Repo-Link:
https://github.com/ryyppy/reason-tictactoe
-----
References:

Effective ML revisited
- https://blog.janestreet.com/effective-ml-revisited

Designing with Types: Making illegal states unrepresentable - Scott Wlaschin
- http://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/

Types and Properties = Software: Designing with Types by Mark Seemann
- http://blog.ploeh.dk/2016/02/10/types-properties-software-designing-with-types/
- https://vimeo.com/162036084

Making impossible states impossible by Richard Feldman
- https://www.youtube.com/watch?v=IcgmSRJHu_8

Back to Basics - Using Flow by A. Sharif
- https://gist.github.com/busypeoples/70d06ba2e2aae65bdd4d5ebbb2fe1488

Making Impossible States Impossible in ReasonML by A. Sharif
- https://gist.github.com/busypeoples/ab2f993843f23614232a1f8500a4b542

Presented on:
6th of September 2017 (ReasonLND)

- Meetup-Page: https://www.meetup.com/ReasonLDN/events/242416734/

1st of February 2018 (ReasonMunich)

- Meetup-Page: https://www.meetup.com/Munich-ReasonML-Meetup/events/245697054/?eventId=245697054
- Video: https://www.youtube.com/watch?v=P7dTPoxCg4w

26th of April 2018 (ReactFinland Conf 2018)

Patrick Stapfer

April 26, 2018
Tweet

More Decks by Patrick Stapfer

Other Decks in Programming

Transcript

  1. View Slide

  2. Patrick Stapfer
    Teaching about Type Systems & JS
    Taming Legacy Codebases
    GraphQL & React

    View Slide

  3. View Slide

  4. View Slide

  5. View Slide

  6. Well Researched Topic
    • Effective ML Revisited - Yaron Minsky

    • Types & Properties = Software: Designing with Types - Mark Seemann
    • Video

    • Designing with Types: Making illegal states unrepresentable - Scott Wlaschin

    • Making impossible states impossible - Richard Feldman

    • Back to the Basics - Using Flow - A.Sharif

    • Making Impossible States Impossible in ReasonML - A. Sharif

    View Slide

  7. Learning By Doing

    (Reason & OCaml)

    View Slide

  8. First Attempt (JS)
    const state = {
    board: [null, ..., null],
    progress: "turn",
    player: "cross",
    };

    View Slide

  9. Add some types w/ Flow…
    type State = {
    board: Board,
    progress: Progress,
    player: Player | null,
    };
    type Token = "cross" | "circle" | "empty";
    type Board = Array;
    type Player = "cross" | "circle";
    type Progress = "turn" | "win" | "draw";

    View Slide

  10. Confusing Design...
    type State = {
    board: Board,
    progress: Progress,
    player: Player | null,
    };
    type Token = "cross" | "circle" | "empty";
    type Board = Array;
    type Player = "cross" | "circle";
    type Progress = "turn" | "win" | "draw";
    const state: State = {
    board: [...],
    progress: "win",
    player: null,
    };
    ???

    View Slide

  11. Refactor Progress & Player Relation
    type State = {
    board: Board,
    progress: Progress,
    player: Player | null,
    };
    type Progress = 

    { type: "turn", player: Player }
    | { type: "win", player: Player }
    | { type: "draw" }

    View Slide

  12. Types in Action (Flow)
    switch (progress.type) {
    case "turn":
    const {player} = progress;
    return `Player ${player}'s turn`;
    case "win":
    const {player} = progress;
    return `Player ${player} won`;
    };
    const progress: Progress = {

    type: "turn",

    player: "cross"

    };

    View Slide

  13. "Pattern Matching" in Flow
    switch (progress.type) {
    case "turn":
    const {player} = progress;
    return `Player ${player}'s turn`;
    case "winner":
    const {player} = progress;
    return `Player ${player} won`;
    };
    switch is no expression
    progress = typeof Object
    flow infer is brittle
    typoooooos
    No checks for exhaustiveness
    verbose syntax

    View Slide

  14. Let's design the state in Reason!
    type state = {
    board: board,
    progress: progress
    };
    type token = Cross | Circle | Empty
    type board = ...
    type player = Cross | Circle
    type progress = 

    | Turn(player)

    | Win(player)

    | Draw

    View Slide

  15. Types in Action (Reason)
    switch (progress) {
    | Turn(p) => "Player" ++ p_to_str(p) ++ "'s turn"
    | Win(p) => "Player" ++ p_to_str(p) ++ "won"
    | Draw => "It's a draw!"
    };
    let progress = Turn(Cross);
    switch (progress.type) {
    case "turn":
    const {player} = progress;
    return `Player ${player}'s turn`;
    case "win":
    const {player} = progress;
    return `Player ${player} won`;
    };

    View Slide

  16. What about the Board? Naive Edition.
    type board = list(token)
    let ok: board = [ Empty, Empty,... , Empty] (9)
    let weird: board = [ Empty, Cross, Circle ]
    let doh: board = [ Empty ]
    + More Generic

    + Kinda Clever
    - Cognitive Load
    - Potentially unsafe code
    - "More tests" to write

    View Slide

  17. Make unrepresentable Board Impossible!
    type board = (row, row, row)
    let r1: row = (Empty, Empty, Empty) /* okay */
    let r2: row = (Empty, Cross) /* fail */
    - More verbose (but dead simple) code
    type row = (token, token, token)
    let r3: row = (Empty, Cross, Cross, Cross) /* fail */
    + Type safe / no surprises
    + "Less tests" to write

    View Slide

  18. Helper types
    type colId = C1 | C2 | C3
    type rowId = R1 | R2 | R3

    View Slide

  19. Get a token from board (Column, Row)
    getToken :: board -> rowId -> colId -> token
    let getToken = (board, rid, cid) => {
    let (r1, r2, r3) = board;
    let fromRow = ((t1, t2, t3), cid) =>
    switch cid {
    | C1 => t1
    | C2 => t2
    | C3 => t3
    };
    switch rid {
    | R1 => fromRow(r1, cid)
    | R2 => fromRow(r2, cid)
    | R3 => fromRow(r3, cid)
    };
    };

    View Slide

  20. Add a token to the board
    let updateColumn = (row, cid, value) => {
    let (t1, t2, t3) = row;
    switch cid {
    | C1 => isEmptyToken(t1) ? (value, t2, t3) : row
    | C2 => isEmptyToken(t2) ? (t1, value, t3) : row
    | C3 => isEmptyToken(t3) ? (t1, t2, value) : row
    };
    };
    let updateBoard = (board, rid, cid, value) => {
    let (r1, r2, r3) = board;
    switch rid {
    | R1 =>
    let r = updateColumn(r1, cid, value);
    (r, r2, r3);
    | R2 =>
    let r = updateColumn(r2, cid, value);
    (r1, r, r3);
    | R3 =>
    let r = updateColumn(r3, cid, value);
    (r1, r2, r);
    };
    }; let isEmptyToken = (square: token) =>
    switch square {
    | Empty => true
    | _ => false
    };

    View Slide

  21. Conclusion
    More Rigid Design
    More KISS than DRY
    Forces edge-cases to be handled
    Alternatives:
    Combine both worlds
    boardToList <-> listToBoard

    View Slide

  22. View Slide

  23. Do we have more time for one more
    thing?

    View Slide

  24. I wanted to show another example
    with async querying... but I had no
    server :-(

    View Slide

  25. INTRODUCING
    TicTacToe as a Service (TTTaaS)

    View Slide

  26. Loader Component™

    type loaderState = {
    data: option(tictactoeState),
    loading: bool
    }

    View Slide

  27. Loader Component™

    type remoteData = 

    | NotAsked

    | Pending

    | Success(Game.tictactoeState)
    | Error(string)
    type loaderState = remoteData
    render: ({state}) =>
    switch state {
    | NotAsked => ("Starting Application" |> se)
    | Loading => ("Loading Game..." |> se)
    | Error(errorMsg) => (errorMsg |> se)
    | Success({board, progress}) =>
    }

    View Slide

  28. http://github.com/ryyppy/reason-tictactoe
    Source Code for Game + Server:
    https://discord.gg/reasonml
    Join our Community

    View Slide

  29. View Slide