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

Elm Conf 2017: Solving the Boolean Identity Crisis

Elm Conf 2017: Solving the Boolean Identity Crisis

Jeremy Fairbank

September 28, 2017
Tweet

More Decks by Jeremy Fairbank

Other Decks in Programming

Transcript

  1. Jeremy Fairbank
    @elpapapollo / jfairbank

    View Slide

  2. Software is broken.
    We are here to fix it.
    Say [email protected]

    View Slide

  3. +
    Follow @elpapapollo
    for future updates

    View Slide

  4. View Slide

  5. { ? }

    View Slide

  6. {
    dog: null,
    }

    View Slide

  7. FETCH?

    View Slide

  8. {
    fetching: true,
    dog: null,
    }

    View Slide

  9. SUCCESS?

    View Slide

  10. {
    fetching: false,
    success: true,
    dog: { name: 'Tucker' },
    }

    View Slide

  11. ERRORS?

    View Slide

  12. {
    fetching: false,
    success: false,
    dog: null,
    error: true,
    errorMessage: 'Ruh roh!',
    }

    View Slide

  13. {
    fetching: true,
    success: true,
    dog: { name: 'Tucker' },
    error: true,
    errorMessage: 'Uh oh!',
    }
    ¯\_(ツ)_/¯
    Invalid State

    View Slide

  14. {
    fetching: true,
    success: true,
    dog: { name: 'Tucker' },
    error: true,
    errorMessage: 'Uh oh!',
    }
    ¯\_(ツ)_/¯
    Invalid State

    View Slide

  15. if (props.error) {
    ...
    } else if (props.fetching) {
    ...
    } else if (props.success) {
    ...
    } else {
    ...
    }

    View Slide

  16. STATE
    fetching
    success error

    View Slide

  17. PRIMITIVE OBSESSION
    USING COMIC SANS IS
    KINDA LIKE PRIMITIVE
    OBSESSION

    View Slide

  18. The Problem with Booleans…

    View Slide

  19. – Robert Harper
    “There is no information carried
    by a Boolean beyond its value,
    and that’s the rub.”

    View Slide

  20. binary data type with
    no inherent meaning
    boolean

    View Slide

  21. bookFlight "St. Louis" True
    ?

    View Slide

  22. ?
    bookFlight "St. Louis" True False True
    ? ?

    View Slide

  23. ?

    View Slide

  24. Boolean Algebra
    George Boole
    x ∧ y
    x ∨ y
    x ∨ (y ∧ z)
    ¬x

    View Slide

  25. Boolean Values
    True && True
    True || False
    True || (True && False)
    not True

    View Slide

  26. Propositional
    Logic

    View Slide

  27. Premise 1: If it’s raining then it’s cloudy.
    Premise 2: It’s raining.
    Conclusion: It’s cloudy.
    Propositional Logic

    View Slide

  28. Premise 1: If it’s raining then it’s cloudy.
    Premise 2: It’s raining.
    Conclusion: It’s cloudy.
    PROPOSITIONS

    View Slide

  29. prop·o·si·tion
    a statement that expresses a concept
    that can be true or false

    View Slide

  30. Boolean ≠ Proposition
    A proposition, p, that is true is not the
    same as saying p is equal to true.

    View Slide

  31. LOSS OF INTENT

    View Slide

  32. LOSS OF INFORMATION

    View Slide

  33. View Slide

  34. bookFlight "St. Louis" True
    ?

    View Slide

  35. bookFlight city isPremiumCustomer =
    if isPremiumCustomer then
    ...
    else
    ...

    View Slide

  36. bookFlight city isPremiumCustomer =
    if isPremiumCustomer then
    ...
    else
    ...

    View Slide

  37. bookFlight city isPremiumCustomer =
    if isPremiumCustomer then
    ...
    else
    ...
    Regular customer?

    View Slide

  38. viewOpacitySlider =
    viewSlider True
    viewVolumeSlider =
    viewSlider False
    view model =
    div []
    [ viewOpacitySlider
    , viewVolumeSlider
    ]
    ?

    View Slide

  39. viewSlider msg isHorizontal =
    if isHorizontal then
    ...
    else
    ...

    View Slide

  40. viewSlider msg isHorizontal =
    if isHorizontal then
    ...
    else
    ... Implicit
    vertical slider

    View Slide

  41. – Uncle Bob (Robert Martin)
    “Boolean arguments loudly
    declare that the function does
    more than one thing. They are
    confusing and should be
    eliminated.”

    View Slide

  42. rotateFuelRod fuelRod 30 True
    rotateControlRod
    ?

    View Slide

  43. rotateFuelRod fuelRod 30 True
    rotateControlRod controlRod 30 True

    View Slide

  44. View Slide

  45. rotateFuelRod fuelRod amount inDegrees =
    ...
    rotateControlRod controlRod amount inRadians =
    ...

    View Slide

  46. rotateFuelRod fuelRod amount inDegrees =
    ...
    rotateControlRod controlRod amount inRadians =
    ...

    View Slide

  47. rotateFuelRod fuelRod amount inDegrees =
    ...
    rotateControlRod controlRod amount inRadians =
    ...

    View Slide

  48. ?
    bookFlight "St. Louis" True False True
    ? ?

    View Slide

  49. bookFlight city isPremiumCustomer hasCheckedLuggage preferWindow =
    let
    luggageCost =
    if hasCheckedLuggage then
    ...
    else
    ...
    in
    if isPremiumCustomer then
    if hasCheckedLuggage then
    ...
    else
    ...
    else if hasCheckedLuggage then
    ...
    else
    ...

    View Slide

  50. View Slide

  51. – Martin Fowler
    “…an API should be written to make it
    easier for the caller, so if we know
    where the caller is coming from we
    should design the API with that
    information in mind.”

    View Slide

  52. Write code for humans
    Not computers

    View Slide

  53. View Slide

  54. bookFlight "St. Louis" "Premium"

    View Slide

  55. bookFlight city customerType =
    case customerType of
    "Premium" -> ...
    "Regular" -> ...
    _ -> ...

    View Slide

  56. bookFlight city customerType =
    case customerType of
    "Premium" -> ...
    "Regular" -> ...
    _ -> ...

    View Slide

  57. bookFlight "St. Louis" "premium"
    bookFlight "St. Louis" "economical"
    bookFlight "St. Louis" ""
    Primitive Obsession Revisited

    View Slide

  58. How do we represent a
    finite domain in Elm?

    View Slide

  59. type CustomerType
    = Premium
    | Regular
    bookFlight "St. Louis" Premium
    bookFlight "St. Louis" Regular

    View Slide

  60. bookFlight city customerType =
    case customerType of
    Premium -> ...
    Regular -> ...

    View Slide

  61. type Angle
    = Degrees Float
    | Radians Float
    rotateFuelRod fuelRod (Degrees 30)
    rotateControlRod controlRod (Radians pi)

    View Slide

  62. rotateFuelRod fuelRod angle =
    case angle of
    Degrees amount -> ...
    Radians amount -> ...
    rotateControlRod controlRod angle =
    case angle of
    Degrees amount -> ...
    Radians amount -> ...

    View Slide

  63. rotate rod angle =
    case angle of
    Degrees amount -> ...
    Radians amount -> ...

    View Slide

  64. rotate rod angle =
    case angle of
    Degrees amount -> ...
    Radians amount -> ...

    View Slide

  65. rotateInDegrees fuelRod 30
    rotateInRadians controlRod pi

    View Slide

  66. rotateInDegrees fuelRod 30
    rotateInRadians controlRod pi
    Still encourages primitive obsession

    View Slide

  67. rotate rod angle =
    case angle of
    Degrees amount ->
    rotateInDegrees rod amount
    Radians amount ->
    rotateInRadians rod amount

    View Slide

  68. Make APIs
    UNDERSTANDABLE and
    CONVENIENT

    View Slide

  69. Function
    True
    False

    View Slide

  70. View Slide

  71. time =
    if doYouKnowTheTime person then
    tellMeTheTime person
    else
    """
    Does anybody really know
    what time it is?
    """

    View Slide

  72. time =
    if doYouKnowTheTime person then
    tellMeTheTime person
    else
    """
    Does anybody really know
    what time it is?
    """

    View Slide

  73. time =
    if doYouKnowTheTime person then
    tellMeTheTime person
    else
    """
    Does anybody really know
    what time it is?
    """

    View Slide

  74. time =
    if doYouKnowTheTime person then
    tellMeTheTime person
    else
    """
    Does anybody really know
    what time it is?
    """

    View Slide

  75. time =
    if doYouKnowTheTime person then
    tellMeTheTime person
    else
    tellMeTheTime person

    View Slide

  76. Boolean Blindness

    View Slide

  77. – Conor McBride
    “To make use of a Boolean you
    have to know its provenance so
    that you can know what it
    means.”

    View Slide

  78. Get your organic,
    homegrown
    booleans from
    Rhode Island!
    Providence, RI

    View Slide

  79. PROVENANCE

    View Slide

  80. function findDogByName(name, dogs) {
    if (name in dogs) {
    return `${name} is a ${dogs[name].breed}`;
    } else {
    return 'Heck! No pupper found.';
    }
    }

    View Slide

  81. findDogByName name dogs =
    if Dict.member name dogs then
    case Dict.get name dogs of
    Just dog ->
    name ++ " is a " ++ dog.breed
    Nothing ->
    "Heck! No pupper found."
    else
    "Heck! No pupper found."

    View Slide

  82. findDogByName name dogs =
    if Dict.member name dogs then
    case Dict.get name dogs of
    Just dog ->
    name ++ " is a " ++ dog.breed
    Nothing ->
    "Heck! No pupper found."
    else
    "Heck! No pupper found."

    View Slide

  83. findDogByName name dogs =
    if Dict.member name dogs then
    case Dict.get name dogs of
    Just dog ->
    name ++ " is a " ++ dog.breed
    Nothing ->
    "Heck! No pupper found."
    else
    "Heck! No pupper found."

    View Slide

  84. canDivide numerator denominator =
    denominator /= 0
    divisionResult numerator denominator =
    if canDivide numerator denominator then
    "The result is "
    ++ toString (numerator / denominator)
    else
    "Could not divide"

    View Slide

  85. canDivide numerator denominator =
    denominator /= 0
    divisionResult numerator denominator =
    if canDivide numerator denominator then
    "The result is "
    ++ toString (numerator / denominator)
    else
    "Could not divide"

    View Slide

  86. divisionResult numerator denominator =
    if canDivide numerator denominator then
    "The result is "
    ++ toString (numerator / denominator)
    else
    "The result is "
    ++ toString (numerator / denominator)

    View Slide

  87. – Dan Licata
    “Boolean tests let you look,
    options let you see.”

    View Slide

  88. Alternative
    Return Values

    View Slide

  89. findDogByName name dogs =
    case Dict.get name dogs of
    Just dog ->
    name ++ " is a " ++ dog.breed
    Nothing ->
    "Heck! No pupper found."

    View Slide

  90. case whatTimeIsIt person of
    Just time -> ...
    Nothing -> ...

    View Slide

  91. case whatTimeIsIt person of
    Just time -> ...
    Nothing -> ...

    View Slide

  92. type TellTime
    = Time String
    | NoWatch
    | NoPhone
    | NoSundial
    case whatTimeIsIt person of
    Time time -> ...
    NoWatch -> ...
    NoPhone -> ...
    NoSundial -> ...

    View Slide

  93. divide numerator denominator =
    case denominator of
    0 -> Err "Divide by zero"
    _ -> Ok (numerator / denominator)
    divisionResult numerator denominator =
    case divide 4 2 of
    Ok result ->
    "The result is " ++ toString result
    Err error ->
    "Could not divide: " ++ error

    View Slide

  94. See with Union Types

    View Slide

  95. { fetching = False
    , success = True
    , dog = Just { name = "Tucker" }
    , error = False
    , errorMessage = ""
    }
    Back to State

    View Slide

  96. if model.error then
    ...
    else if model.fetching then
    ...
    else if model.success then
    ...
    else
    ...

    View Slide

  97. if model.fetching then
    ...
    else if model.success then
    ...
    else
    ...

    View Slide

  98. { fetching = True
    , success = True
    , dog = { name = "Tucker" }
    , error = True
    , errorMessage = "Uh oh!"
    }

    View Slide

  99. https://youtu.be/IcgmSRJHu_8

    View Slide

  100. STATE
    fetching
    success error

    View Slide

  101. type RemoteDoggo
    = Ready
    | Fetching
    | Success Dog
    | Error String
    type alias Model =
    { dog : RemoteDoggo
    , ...
    }

    View Slide

  102. type RemoteDoggo
    = Ready
    | Fetching
    | Success Dog
    | Error String
    type alias Model =
    { dog : RemoteDoggo
    , ...
    }

    View Slide

  103. type RemoteDoggo
    = Ready
    | Fetching
    | Success Dog
    | Error String
    type alias Model =
    { dog : RemoteDoggo
    , ...
    }

    View Slide

  104. type RemoteDoggo
    = Ready
    | Fetching
    | Success Dog
    | Error String
    type alias Model =
    { dog : RemoteDoggo
    , ...
    }

    View Slide

  105. { dog = Ready, ... }
    { dog = Fetching, ... }
    { dog = Success { name = "Tucker" }, ... }
    { dog = Error "Uh oh!", ... }

    View Slide

  106. view : Model -> Html Msg
    view model =
    case model.dog of
    Ready -> viewFindDog model
    Fetching -> viewSpinner
    Success dog -> viewDog dog
    Error error -> viewError error

    View Slide

  107. view : Model -> Html Msg
    view model =
    case model.dog of
    Ready -> viewFindDog model
    Fetching -> viewSpinner
    Success dog -> viewDog dog
    -- Error error -> viewError error

    View Slide

  108. This `case` does not have branches for all possibilities.
    47|> case model.dog of
    48|> Ready -> viewFindDog model
    49|>
    50|> Fetching -> viewSpinner
    51|>
    52|> Success dog -> viewDog dog
    You need to account for the following values:
    Main.Error _
    Add a branch to cover this pattern!

    View Slide

  109. package.elm-lang.org/packages/krisajenkins/remotedata/latest
    type RemoteData e a
    = NotAsked
    | Loading
    | Failure e
    | Success a

    View Slide

  110. package.elm-lang.org/packages/krisajenkins/remotedata/latest
    type RemoteData e a
    = NotAsked
    | Loading
    | Failure e
    | Success a

    View Slide

  111. package.elm-lang.org/packages/krisajenkins/remotedata/latest
    type RemoteData e a
    = NotAsked
    | Loading
    | Failure e
    | Success a

    View Slide

  112. type alias Todo =
    { isComplete : Bool
    , isEditing : Bool
    }
    type alias Customer =
    { isPlatinum : Bool
    , isGold : Bool
    , isSilver : Bool
    }

    View Slide

  113. type TodoStatus
    = Complete
    | Editing
    type alias Todo =
    { status : TodoStatus }

    View Slide

  114. type AccountType
    = Platinum
    | Gold
    | Silver
    type alias Customer =
    { accountType : AccountType }

    View Slide

  115. UT
    Impossible state becomes impossible*
    Code becomes understandable
    *trademark Richard Feldman

    View Slide

  116. But wait,
    There’s MORE!
    AS SEEN ON
    U I

    View Slide

  117. Save to archive

    View Slide

  118. Save to archive
    A wild checkbox appears!

    View Slide

  119. Save to archive

    View Slide

  120. Save to archive
    ?

    View Slide

  121. View Slide

  122. We design APIs for callers
    So we should design UIs for users

    View Slide

  123. View Slide

  124. hu·mane
    having or showing compassion or
    benevolence

    View Slide

  125. Save to archive
    ?

    View Slide

  126. Save to archive
    Save to inbox

    View Slide

  127. Save to archive
    Save to inbox

    View Slide

  128. Save to archive
    Save to inbox
    Verb labels = when will it happen?

    View Slide

  129. Saved to archive
    Saved to inbox
    Adjective labels = describe the final result.

    View Slide

  130. Goodbye, boolean?

    View Slide

  131. B A L A N C E

    View Slide

  132. EMPATHY

    View Slide

  133. Jeremy Fairbank
    @elpapapollo / jfairbank
    Slides: bit.ly/elm-bool

    View Slide