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

Lambda Squared 2019: Solving the Boolean Identity Crisis

Lambda Squared 2019: Solving the Boolean Identity Crisis

Powerful in its simplicity, the boolean can be limiting. In this talk, we will look at examples where booleans obscure the meaning of code and make it harder to maintain. You will learn patterns such as ADTs to write clear code without booleans and gain greater empathy for your teammates and users.

Jeremy Fairbank

April 26, 2019
Tweet

More Decks by Jeremy Fairbank

Other Decks in Programming

Transcript

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

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

    error: true, errorMessage: 'Uh oh!', } ¯\_(ツ)_/¯ Invalid State
  3. if (props.error) { ... } else if (props.fetching) { ...

    } else if (props.success) { ... } else { ... }
  4. – Robert Harper “There is no information carried by a

    Boolean beyond its value, and that’s the rub.”
  5. ?

  6. Premise 1: If it’s raining then it’s cloudy. Premise 2:

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

    It’s raining. Conclusion: It’s cloudy. PROPOSITIONS
  8. Boolean ≠ Proposition A proposition, p, that is true is

    not the same as saying p is equal to true.
  9. 4

  10. – Robert Martin “Boolean arguments loudly declare that the function

    does more than one thing. They are confusing and should be eliminated.”
  11. bookFlight city isPremiumCustomer hasCheckedLuggage preferWindow = let luggageCost = if

    hasCheckedLuggage then ... else ... in if isPremiumCustomer then if hasCheckedLuggage then ... else ... else if hasCheckedLuggage then ... else ...
  12. – 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.”
  13. type Angle = Degrees Float | Radians Float rotateFuelRod fuelRod

    (Degrees 30) rotateControlRod controlRod (Radians pi)
  14. rotateFuelRod fuelRod angle = case angle of Degrees amount ->

    ... Radians amount -> ... rotateControlRod controlRod angle = case angle of Degrees amount -> ... Radians amount -> ...
  15. rotate rod angle = case angle of Degrees amount ->

    rotateInDegrees rod amount Radians amount -> rotateInRadians rod amount
  16. time = if doYouKnowTheTime person then tellMeTheTime person else """

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

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

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

    Does anybody really know what time it is? """
  20. – Conor McBride “To make use of a Boolean you

    have to know its provenance so that you can know what it means.”
  21. function findDogByName(name, dogs) { if (name in dogs) { return

    `${name} is a ${dogs[name].breed}`; } else { return 'Heck! No pupper found.'; } }
  22. 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."
  23. 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."
  24. 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."
  25. canDivide numerator denominator = denominator /= 0 divisionResult numerator denominator

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

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

    result is " ++ toString (numerator / denominator) else "The result is " ++ toString (numerator / denominator)
  28. findDogByName name dogs = case Dict.get name dogs of Just

    dog -> name ++ " is a " ++ dog.breed Nothing -> "Heck! No pupper found."
  29. type TellTime = Time String | NoWatch | NoPhone |

    NoSundial case whatTimeIsIt person of Time time -> ... NoWatch -> ... NoPhone -> ... NoSundial -> ...
  30. 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
  31. { fetching = False , success = True , dog

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

    = { name = "Tucker" } , error = True , errorMessage = "Uh oh!" }
  33. type RemoteDoggo = Ready | Fetching | Success Dog |

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

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

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

    Error String type alias Model = { dog : RemoteDoggo , ... }
  37. { dog = Ready, ... } { dog = Fetching,

    ... } { dog = Success { name = "Tucker" }, ... } { dog = Error "Uh oh!", ... }
  38. view : Model -> Html Msg view model = case

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

    model.dog of Ready -> viewFindDog model Fetching -> viewSpinner Success dog -> viewDog dog -- Error error -> viewError error
  40. 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!
  41. type alias Todo = { isComplete : Bool , isEditing

    : Bool } type alias Customer = { isPlatinum : Bool , isGold : Bool , isSilver : Bool }
  42. type AccountType = Platinum | Gold | Silver type alias

    Customer = { accountType : AccountType }