scala-logo-small

Prefer types over booleans

In a recent post I represented whether a TV was on or off as an enum in Java and case objects in Scala. Someone asked me why you would do either one when you could simply represent it as a boolean.

The same reasoning would seem to extend to any situation with two choices:

  1. Male/Female
  2. On/Off
  3. In/Out
  4. Active/Inactive
  5. Hot/Cold
  6. Light/Dark
  7. etc..

I have a rule:

prefer types over booleans

This post is about why.

Summary of the reasons
My main reason for this rule is I don’t trust how much little the compiler knows, based on a boolean.

My second reason for this rule is I don’t trust how much little the code says to future programmers, based on a boolean.

First reason, how little the compiler knows
What is the point of a compiler? As the one writing the code the main advantage I get is that any mistakes I make might be caught sooner rather than later. Errors prevented at compile time are the best kind of prevented errors!

So let’s consider I write the following method one day:

def complement(maleOrFemale: Boolean, dayOrNight: Boolean): String = {
  if(maleOrFemale && dayOrNight) {
    "You look handsome today!"
  } else if(!maleOrFemale && dayOrNight) {
    "You look beautiful today!"
  } else if(maleOrFemale && !dayOrNight) {
    "You look handsome tonight!"
  } else {
    "You look beautiful tonight!"
  }
}

But let’s pretend that I make a mental mistake elsewhere:

val complementForValerieToday = complement(true, true)

The compiler doesn’t blink on this. It has no concept that we say “handsome” for men and “beautiful” for women because we haven’t given it any information about that. We’re just relying on the downstream effects of some boolean logic. The booleans themselves only tell the compiler true and false. Nothing more. We kind of dropped some meaning down on top of it at the end there.

A much better arrangement in Scala would be like this:


sealed trait Gender
case object Male extends Gender
case object Female extends Gender

sealed trait DayOrNight
case object Day extends DayOrNight
case object Night extends DayOrNight

def complement(gender: Gender, when: DayOrNight): String = {
  (gender, when) match {
    case (Male, Day) => "You look handsome today!"
    case (Female, Day) => "You look beautiful today!"
    case (Male, Night) => "You look handsome tonight!"
    case (Female, Night) => "You look beautiful tonight!"
  }
}

Code that calls this new complement method must look like one of these options:

complement(Male, Day)
complement(Male, Night)
complement(Female, Day)
complement(Female, Night)

Or you can abstract it away as in this REPL output:

scala> val scott = Male
scott: Male.type = Male

scala> val whenItsDark = Night
whenItsDark: Night.type = Night

scala> complement(scott, whenItsDark)
res3: String = You look handsome tonight!

Either way, the compiler is not going to let you make the mistake of complementing Valerie with the word “handsome” and saying “day” when it’s night!

Second reason, how little the code says to future programmers
In our first example above, there’s a variable called “dayOrNight” and its of type Boolean. Tell me, which value, true or false, represents “Day” in this scenario?

You might change the variable name to something more explicit like “isDay”. By doing so are you sure that solves the problem? Pretend you actually present this example to someone and say, “the variable is named “isDay” and when it is true, it is daytime. What do you think this variable being false means?

I hope you see the problem I’m getting at.

The explicit solution we arrived at eventually is definitely better for future readers of your code:

sealed trait DayOrNight
case object Day extends DayOrNight
case object Night extends DayOrNight

This also presents a common class of errors called “wrong order bugs.”

A wrong order bug is what would occur if you attempted to call the initial complement method like this:

//remember, method signature is def complement(maleOrFemale: Boolean, dayOrNight: Boolean): String
val complementForValerieToday = complement(isDay, isFemale)

Not only does this compile but also whenever a future programmer is reading this code they can’t see this bug unless they observe it at runtime and / or have the method signature of complement memorized.

Our second go at it was much better, because then it wouldn’t even compile to begin with!

//method signature is def complement(gender: Gender, when: DayOrNight): String
val complementForValerieToday = complement(Day, Female)

Leave a Reply

Your email address will not be published. Required fields are marked *