I prefer to let the language I’m using think on my behalf as much as
possible. Ideally, the language is rich enough that the proof of
correctness is inherent in the code I’ve written. Even when I use looser
languages, these principled languages inform the structure of my code.
To make this a bit more let’s turn our focus to if
,
else
, and purity.
A cool way to understand purity is using what’s known as a “modal
separation.” This is a really fancy way to say that we have expressions which are pure and
always evaluate to a value, alongside commands which are impure and
are executed for their side effects. If you’ve ever used Haskell, you’re
already familiar with this notion—we only need do
notation
when we need to write impure (or “monadic”) code.
In an expression language, every if
must have an else
; for the entire
if
expression to be used as a value, both branches must in
turn evaluate to values. It’s only when we move to a language with
commands where it makes sense to allow omitting the else
branch. if
expressions are not some abstract concept;
chances are you’ve encountered them under the name “the ternary
operator.”
An if
statement (as opposed to an
if
expression) is a command; it’s useful for running
side-effectful code. Sometimes, we don’t want one of the branches to
have any side effects (for example, because the state of the world
doesn’t need to be changed). Languages with commands allow omitting the
else
.
What does this mean for us? Since expression languages form the basis
for purity, every pure function can be written where the if
is matched with an else
. Put another way, an unmatched
if
is a likely indicator that the code I’ve written is
impure.
This makes me more aware of when I’m dealing with impure code. For example, I might want to factor out as much of the pure code into a separate helper function. There’s a time and a place for impure code. But since pure code is more composable and easier to test, it’s best to factor the impure code out whenever possible.
In a principled language, there’s a distinction between
if
expressions and if
statements. On the other
hand, some language only have one, or they blur the line between the
two. We can draw upon our experiences with languages that are rigorous
about minutia like this to better inform how we write clean code.