Nate Meyvis

Notes on optional booleans

Sometimes we need to represent things that are of some type if they (in some sense) exist, but might not exist. Optionals address this problem.

Let's say you have a testimonial variable that is a string if there is a testimonial, but that might not exist. (Maybe it represents the contents of a non-required form field.) One common approach is to make testimonial a string, and to make the string empty in the case of a nonexistent testimonial. The optional approach--whether you're using a built-in language construct or rolling your own by defining something like string | null--almost always works better:

  1. It's more semantically accurate.
  2. It allows you to distinguish the empty string from the no-testimonial case. You could imagine an empty testimonial: maybe the testimonial is for a seminar about keeping vows of silence. There are all sorts of other contexts where an empty item is different from a nonexistent one. (An empty text message means something very different from no text message.)
  3. It allows you to express the emptiness in the type system, so that you can use the type system's resources to distingiush between empty and non-empty values. (Perhaps you only want to allow strings, not empty values, to be passed to functions that will send emails with those strings as subject lines.)
  4. Modern languages tend to give you lots of ergonomic goodies for doing common operations on optionals (e.g., filtering empty values out when you're iterating over an iterable of optionals).

Optionals are, then, a useful tool. Optionals of booleans, however, tend to cause trouble:

  1. Both the absent value and the present-but-false value will be falsy (in any major paradigm that supports optionals), so you often need to write bug-prone helpers when you need to distinguish between these.
  2. It seems that optional booleans simply break humans' brains; for whatever reason, I've seen these cause problems far out of proportion to their seeming complexity.

There are at least two alternatives worth considering:

  1. An enum with ABSENT, FALSE, and TRUE (or similar) cases;
  2. A simple boolean (because sometimes you don't, after all, have a meaningful distinction between the absent and false cases).

I'm not recommending that you never use optional booleans. Perhaps you have a ton of non-required form fields, and you're representing all of them as optionals: in this case, consistency tells in favor of representing optional boolean fields as optional booleans. Use your best judgement.

But if you do decide in favor of optional booleans, please:

  1. Be careful around true/false checks;
  2. Be quick to factor their logic into well-tested helper functions;
  3. Comment the relevant code liberally.