Jeremy Fairbank
April 26, 2019
60

# 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.

April 26, 2019

## Transcript

1. Jeremy Fairbank
@elpapapollo / jfairbank

2. @testdouble helps improves
how the world build software.
testdouble.com

3. bit.ly/programming-elm

4. elm

5. { ? }

6. {
dog: null,
}

7. FETCH?

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

9. SUCCESS?

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

11. ERRORS?

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

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

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

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

16. STATE
fetching
success error

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

18. The Problem with Booleans…

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

20. binary data type with
no inherent meaning
boolean

21. bookFlight "TYS" True
?

22. bookFlight "TYS" True
?

23. ?
bookFlight "TYS" True False True
? ?

24. ?

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

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

27. Propositional
Logic

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

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

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

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

32. LOSS OF INTENT

33. LOSS OF INFORMATION

34. 4

35. bookFlight "TYS" True
?

...
else
...

...
else
...

...
else
...
Regular customer?

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

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

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

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

43. rotateFuelRod fuelRod 30 True
rotateControlRod
?

44. rotateFuelRod fuelRod 30 True
rotateControlRod controlRod 30 True

45. rotateFuelRod fuelRod amount inDegrees =
...
...

46. rotateFuelRod fuelRod amount inDegrees =
...
...

47. rotateFuelRod fuelRod amount inDegrees =
...
...

48. ?
bookFlight "TYS" True False True
? ?

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

50. – 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.”

51. Write code for humans
Not computers

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

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

bookFlight "TYS" "economical"
bookFlight "TYS" ""
Primitive Obsession Revisited

56. Represent a finite
domain in Elm

57. type CustomerType
| Regular
bookFlight "TYS" Regular

58. bookFlight city customerType =
case customerType of
Regular -> ...

59. type Angle
= Degrees Float
rotateFuelRod fuelRod (Degrees 30)

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

61. rotate rod angle =
case angle of
Degrees amount -> ...

62. rotate rod angle =
case angle of
Degrees amount -> ...

63. rotateInDegrees fuelRod 30

64. rotateInDegrees fuelRod 30
Still encourages primitive obsession

65. rotate rod angle =
case angle of
Degrees amount ->
rotateInDegrees rod amount

66. Make APIs
UNDERSTANDABLE and
CONVENIENT

67. Function
True
False

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

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

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

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

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

73. Boolean Blindness

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

homegrown
booleans from
Rhode Island!
Providence, RI

76. PROVENANCE

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

78. 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."

79. 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."

80. 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."

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

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

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

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

85. Alternative
Return Values

86. type Maybe a
= Just a
| Nothing

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

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

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

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

91. 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

92. See with Custom Types

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

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

95. if model.fetching then
...
else if model.success then
...
else
...

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

97. https://youtu.be/IcgmSRJHu_8

98. STATE
fetching
success error

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

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

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

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

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

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

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

106. This `case` does not have branches for all possibilities.
47|> case model.dog of
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!

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

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

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

110. UT
Impossible state becomes impossible*
Code becomes understandable

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

112. Save to archive

113. Save to archive
A wild checkbox appears!

114. Save to archive

115. Save to archive
?

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

117. hu·mane
having or showing compassion or
benevolence

118. Save to archive
?

119. Save to archive
Save to inbox

120. Save to archive
Save to inbox

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

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

123. Goodbye, boolean?

124. B A L A N C E

125. EMPATHY

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