Minimize boolean usage

Boolean ambiguity can lead to loss of intent and loss of information in our code, resulting in ambiguous logic. To avoid this issue, we should prefer self-documenting alternatives whenever the intention is unclear.

This pattern is inspired by Jeremy Fairbank's "Solving the Boolean Identity Crisis" talk and series of articles, where he explains the rationale and the pattern in great details, if you want to delve deeper.

Boolean Ambiguity

Boolean arguments can make code confusing and harder to maintain by hiding the intent of code.

Anti-Pattern 1

What is the significance of the True value here without looking up the definition of bookFlight?

bookFlight "ELM" True

Pattern 1

We can clean up the bookFlight function by replacing the boolean arguments with a Custom Type:

type CustomerStatus
    = Premium
    | Regular
    | Economy

Now calls to bookFlight declare the intent of code because we pass in the CustomerStatus directly:

bookFlight "ELM" Premium

Boolean Blindness

Returning Bool from a function can lead to boolean blindness. This happens because we get a value that the type system cannot use for enforcing further logic in the program.

Anti-Pattern 2

For example:

time =
    if isValid formData then
        submitForm formData

    else
        showErrors formData

In this snippet isValid only returns a Bool. We can call submitForm with the original formData. The compiler wouldn't complain if we swap the if-else branches.

Pattern 2

Replace boolean return values with custom types to eliminate boolean blindness and leverage the compiler for safer code. In this example we do it with a Result Error Type:

time =
    case isValid formData of
        Ok validFormData ->
            submitForm validFormData

        Err errors ->
            showErrors errors

In this case submitForm is a function that can only be called with valid form data.