Do not use implicit truthiness and falsiness

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the dumb-mistakes-and-gotchas category.

Last Updated: 2024-04-19

The lesson in this piece is intended for programmers who work in more than one programming language.

I was writing software to limit the dynamic range (i.e. note volumes) in midi files. The following loop that was supposed to take different actions depending on whether or not an "observation" (of a midi event) had already been made (observation_already_made)

def calibrate_existing_dynamic_range(midi_data):
    min_observed = None
    max_observed = None
    for message in track:
        if message.type == 'note_on':
            observation_already_made = max_observed and min_observed
            if observation_already_made:
                max_observed = max(message.velocity, max_observed)
                min_observed = min(message.velocity, min_observed)
            else:
                max_observed = message.velocity
                min_observed = message.velocity

The code was not acting as expected. When I looked at the data, most of the values for min_observed were 0. In the Ruby language, my first programming language and therefore my template for others, 0 is truthy. So if the max_observed was even 0 then observation_already_made = max_observed and min_observed would evaluate to 0, which would be truthy.

However: 0 isn't truthy in Python! It is falsey. Therefore the observation_already_made was never set to something truthy.

What I should have done instead was check that there was something other than null/none:

observation_already_made = max_observed is not None and min_observed is not None

Lesson

In general, truthiness rules differ across languages and it's easy to get tripped up when you switch languages often (as I suspect many programmers end up doing). Therefore it's wisest to explicitly test against null etc. rather than rely on implicit truthiness rules.