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

Connect.Tech 2019: Solving the Boolean Identity Crisis

Connect.Tech 2019: Solving the Boolean Identity Crisis

While powerful in its simplicity and important to computation, the boolean can be limiting in applications. In this talk, briefly explore the history of boolean logic in computation and look at how booleans can become misused. See examples where booleans obscure the meaning of code, make code harder to maintain, and hinder usability for your teammates and users. You will learn how to harness techniques from functional programming languages in the world of JavaScript to write clearer code. Most importantly, you will learn how to place usability at the forefront of the APIs and UIs you build.

Jeremy Fairbank

October 17, 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. const OpacitySlider = createSlider(true) const VolumeSlider = createSlider(false) const App

    = () => ( <div> <OpacitySlider /> <VolumeSlider /> </div> ) ?
  11. const createSlider = isHorizontal => () => { if (isHorizontal)

    { ... } else { ... } } Implicit vertical slider
  12. – Robert Martin “Boolean arguments loudly declare that the function

    does more than one thing. They are confusing and should be eliminated.”
  13. function bookFlight(airport, isPremium, hasCheckedLuggage, preferWindow) { const luggageCost = hasCheckedLuggage

    ? ... : ... if (isPremium) { if (hasCheckedLuggage) { ... } else { ... } } else if (hasCheckedLuggage) { ... } else { ... } }
  14. – 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.”
  15. const CustomerType = { Premium: 'Premium', Regular: 'Regular', } bookFlight('ATL',

    CustomerType.Premium) bookFlight('ATL', CustomerType.Regular)
  16. enum CustomerType { Premium = 'Premium', Regular = 'Regular', }

    bookFlight('ATL', CustomerType.Premium) bookFlight('ATL', CustomerType.Regular)
  17. function bookFlight( airport: string, customerType: CustomerType, ): Promise<Flight> { switch

    (customerType) { case CustomerType.Premium: ... case CustomerType.Regular: ... } }
  18. function bookFlight( airport: string, customerType: CustomerType, ): Promise<Flight> { switch

    (customerType) { case CustomerType.Premium: ... case CustomerType.Regular: ... } }
  19. function bookFlight( airport: string, customerType: CustomerType, ): Promise<Flight> { switch

    (customerType) { case CustomerType.Premium: ... case CustomerType.Regular: ... } }
  20. const AngleType = { Degrees: 'Degrees', Radians: 'Radians', } const

    degrees = value => ({ kind: AngleType.Degrees, value }) const radians = value => ({ kind: AngleType.Radians, value }) rotateFuelRod(fuelRod, degrees(30)) rotateControlRod(controlRod, radians(Math.PI))
  21. const AngleType = { Degrees: 'Degrees', Radians: 'Radians', } const

    degrees = value => ({ kind: AngleType.Degrees, value }) const radians = value => ({ kind: AngleType.Radians, value }) rotateFuelRod(fuelRod, degrees(30)) rotateControlRod(controlRod, radians(Math.PI))
  22. const AngleType = { Degrees: 'Degrees', Radians: 'Radians', } const

    degrees = value => ({ kind: AngleType.Degrees, value }) const radians = value => ({ kind: AngleType.Radians, value }) rotateFuelRod(fuelRod, degrees(30)) rotateControlRod(controlRod, radians(Math.PI))
  23. const AngleType = { Degrees: 'Degrees', Radians: 'Radians', } const

    degrees = value => ({ kind: AngleType.Degrees, value }) const radians = value => ({ kind: AngleType.Radians, value }) rotateFuelRod(fuelRod, degrees(30)) rotateControlRod(controlRod, radians(Math.PI))
  24. enum AngleType { Degrees = 'Degrees', Radians = 'Radians', }

    type Angle = { kind: AngleType, value: number, }
  25. enum AngleType { Degrees = 'Degrees', Radians = 'Radians', }

    type Angle = { kind: AngleType, value: number, }
  26. enum AngleType { Degrees = 'Degrees', Radians = 'Radians', }

    type Angle = { kind: AngleType, value: number, }
  27. function rotateFuelRod( fuelRod: FuelRod, angle: Angle, ): FuelRod { switch

    (angle.kind) { case AngleType.Degrees: ... case AngleType.Radians: ... } }
  28. function rotate( rod: Rod, angle: Angle, ): Rod { switch

    (angle.kind) { case AngleType.Degrees: ... case AngleType.Radians: ... } }
  29. function rotate( rod: Rod, angle: Angle, ): Rod { switch

    (angle.kind) { case AngleType.Degrees: ... case AngleType.Radians: ... } }
  30. function rotate( rod: Rod, angle: Angle, ): Rod { switch

    (angle.kind) { case AngleType.Degrees: ... case AngleType.Radians: ... } }
  31. function rotate( rod: Rod, angle: Angle, ): Rod { switch

    (angle.kind) { case AngleType.Degrees: return rotateInDegrees(rod, angle.value) case AngleType.Radians: return rotateInRadians(rod, angle.value) } }
  32. const matchingStrings = (case_, pattern, strings) => strings.filter(string => {

    switch (case_) { case Case.CaseSensitive: return string.includes(pattern) case Case.CaseInsensitive: return string .toUpperCase() .includes(pattern.toUpperCase()) } })
  33. const matchingStrings = (case_, pattern, strings) => strings.filter(string => {

    switch (case_) { case Case.CaseSensitive: return string.includes(pattern) case Case.CaseInsensitive: return string .toUpperCase() .includes(pattern.toUpperCase()) } })
  34. const matchingStrings = (case_, pattern, strings) => strings.filter(string => {

    switch (case_) { case Case.CaseSensitive: return string.includes(pattern) case Case.CaseInsensitive: return string .toUpperCase() .includes(pattern.toUpperCase()) } })
  35. const caseInsensitive = string => string.toUpperCase() const caseSensitive = string

    => string matchingStrings( caseInsensitive, 'foo', ['FOOBAR', 'foobar'], ) matchingStrings( caseSensitive, 'foo', ['FOOBAR', 'foobar'], )
  36. const caseInsensitive = string => string.toUpperCase() const caseSensitive = string

    => string matchingStrings( caseInsensitive, 'foo', ['FOOBAR', 'foobar'], ) matchingStrings( caseSensitive, 'foo', ['FOOBAR', 'foobar'], )
  37. const caseInsensitive = string => string.toUpperCase() const caseSensitive = string

    => string matchingStrings( caseInsensitive, 'foo', ['FOOBAR', 'foobar'], ) matchingStrings( caseSensitive, 'foo', ['FOOBAR', 'foobar'], )
  38. const matchingStrings = ( normalize, pattern, strings ) => strings.filter(string

    => normalize(string).includes(normalize(pattern)) )
  39. const matchingStrings = ( normalize, pattern, strings ) => strings.filter(string

    => normalize(string).includes(normalize(pattern)) )
  40. const matchingStrings = ( normalize, pattern, strings ) => strings.filter(string

    => normalize(string).includes(normalize(pattern)) )
  41. const bookPremiumCustomer = () => { ... } const bookRegularCustomer

    = () => { ... } bookFlight('ATL', bookPremiumCustomer) bookFlight('ATL', bookRegularCustomer)
  42. – Conor McBride “To make use of a Boolean you

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

    `${name} is a ${dogs[name].breed}`; } else { return 'Heck! No pupper found.'; } }
  44. function canDivide(denominator) { return denominator != 0 } function divisionResult(numerator,

    denominator) { if (canDivide(denominator)) { return `The result is ${numerator / denominator}` } else { return 'Could not divide' } }
  45. function canDivide(denominator) { return denominator != 0 } function divisionResult(numerator,

    denominator) { if (canDivide(denominator)) { return `The result is ${numerator / denominator}` } else { return 'Could not divide' } }
  46. function divisionResult(numerator, denominator) { if (canDivide(denominator)) { return `The result

    is ${numerator / denominator}` } else { return `The result is ${numerator / denominator}` } }
  47. import { Maybe } from 'true-myth' Maybe.just(42) // Just 42

    Maybe.nothing() // Nothing Maybe.of(42) // Just 42 Maybe.of(null) // Nothing Maybe.of(undefined) // Nothing
  48. const divide = (numerator, denominator) => denominator === 0 ?

    Result.err('Divide by zero') : Result.ok(numerator / denominator) const divisionResult = (numerator, denominator) => divide(numerator, denominator) .mapOrElse( error => `Could not divide: ${error}`, result => `The result is ${result}`, )
  49. const divide = (numerator, denominator) => denominator === 0 ?

    Result.err('Divide by zero') : Result.ok(numerator / denominator) const divisionResult = (numerator, denominator) => divide(numerator, denominator) .mapOrElse( error => `Could not divide: ${error}`, result => `The result is ${result}`, )
  50. const divide = (numerator, denominator) => denominator === 0 ?

    Result.err('Divide by zero') : Result.ok(numerator / denominator) const divisionResult = (numerator, denominator) => divide(numerator, denominator) .mapOrElse( error => `Could not divide: ${error}`, result => `The result is ${result}`, )
  51. Back to State { fetching: false, success: true, dog: {

    name: "Tucker" }, error: False, errorMessage: "", }
  52. if (props.error) { ... } else if (props.fetching) { ...

    } else if (props.success) { ... } else { ... }
  53. { fetching: true, success: true, dog: { name: "Tucker" },

    error: true, errorMessage: "Uh oh!", }
  54. const ready = () => ({ status: RemoteDoggo.Ready }) const

    fetching = () => ({ status: RemoteDoggo.Fetching }) const success = value => ({ status: RemoteDoggo.Success, value }) const fail = message => ({ status: RemoteDoggo.Fail, message })
  55. const ready = () => ({ status: RemoteDoggo.Ready }) const

    fetching = () => ({ status: RemoteDoggo.Fetching }) const success = value => ({ status: RemoteDoggo.Success, value }) const fail = message => ({ status: RemoteDoggo.Fail, message })
  56. { dog: ready(), ... } { dog: fetching(), ... }

    { dog: success({ name: 'Tucker' }), ... } { dog: fail('Uh oh!'), ... }
  57. function App({ dog }) { switch (dog.status) { case RemoteDoggo.Ready:

    return <FindDog /> case RemoteDoggo.Fetching: return <Spinner /> case RemoteDoggo.Success: return <Dog dog={dog.value} /> case RemoteDoggo.Fail: return <Error message={dog.message} /> } }
  58. function App({ dog }) { switch (dog.status) { case RemoteDoggo.Ready:

    return <FindDog /> case RemoteDoggo.Fetching: return <Spinner /> case RemoteDoggo.Success: return <Dog dog={dog.value} /> case RemoteDoggo.Fail: return <Error message={dog.message} /> } }
  59. enum Status { Ready = 'Ready', Fetching = 'Fetching', Success

    = 'Success', Fail = 'Fail', } type RemoteDoggo = { kind: Status.Ready } | { kind: Status.Fetching } | { kind: Status.Success, value: Dog } | { kind: Status.Fail, message: string }
  60. function App({ dog }: AppProps): React.ReactElement { switch (dog.kind) {

    case Status.Ready: return <FindDog /> case Status.Fetching: return <Spinner /> case Status.Success: return <Dog dog={dog.value} /> // case Status.Fail: // return <Error message={dog.message} /> } }
  61. app.tsx:20:34 - error TS2366: Function lacks ending return statement and

    return type does not include 'undefined'. 20 function App({ dog }: AppProps): React.ReactElement { ~~~~~~~~~~~~~~~~~~ Found 1 error.