Why are empty lists, strings, tuples and 0 considered truthy?

Languages I’m familiar with, C/C++, Python, will at least consider 0 falsey. Python goes further to consider any empty collection falsey. I’m curious why in Elixir only false and nil as falsey

1 Like

My take:

I think the best way to answer this is to think from the other direction: Why is anything other than false considered falsy?

The absolute simplest thing is that only false is falsy. This is the easiest to understand, but not necessarily the most expressive. Every non-false value added to the falsy-list makes conditions more difficult to understand, so there should be an argument for each one that outweighs any arguments against, plus the inherent complexity of anything not false being falsy!

Let’s consider the empty list:

The biggest argument for, in my mind, is that it (arguably) simplifies certain conditionals in code traversing lists. So you may theoretically have:

def process(list) do
  if list do
    # list is non-empty
  else
    # list is empty
  end
end

But we already have structural pattern matching and multiple function heads, so the above could be written as either:

def process([]) do
  # list is empty
end

def process(list) do
  # list is non-empty
end

# or

def process(list) do
  case list do
    [] -> # list is empty
    non_empty -> # list is non-empty
  end
end

This means that empty-list falsiness does not provide an expressive benefit over existing tools in the language, and shouldn’t be included. The same can be said for 0, "", {}.

An additional argument against empty-collection falsiness is that, unlike Python, Elixir does not support operator overloading, so user-defined collections could never share the same behavior as basic collections. (And there are even built-in collections like the :queue module from Erlang that are implemented using composite basic collections, and would not be able to be falsy here.)

To sum up: there is a strong argument that the expressiveness provided by expanded falsiness is already available using other language features, and there are benefits to keeping conditionals simple.

1 Like

Never been a fan of this.in some programming languages.

1 Like

It is not that 0 is “falsy” in C, it is the only possible way to express what haskell would call False.

C++ has kept this solely for compatibility reasons, despite the fact that they could restrict if and similar to “true” booleans.

Python even has __bool__() magic method, which allows you to define your own understanding of “truthiness” and “falsyness”, which in my past has caused me hour long debugging sessions.

We went as far, that we introduced coding rules and static analyzing enforcing them, that we always had to explicitley check for a variables type to be boolean before actually testing whether its true.

  1. We used mypy for typing.
  2. we forbid to if foo: if foo was not typed bool
  3. we forbid to use bool(x) for any x
  4. for any x that could be bool by spec but was allowed to other types as well, we required a x is bool check before.
  5. for any other type we required explicit equality checks

As annoying those rules were when we introduced them, after we got used to them and applied this rule without exception, we spends much less time fixing bugs and issues that boiled down to “weird falsyness”.

During studies, I quickly learned, foo != NULL is so much easier to read than !foo and the compiler will treat both the same anyway regarding the emitted assembly.

At the same time foo == NULL vs foo == 0 signals the additional intend of “this is pointer” and “this is number”.

And as C only knows about integers and floats in a variety of sizes and does not know any other type anyway, it is a rather bad example in this regard.

I consider it a pity that there are truthy and falsy values besides false and true in elixir and consider it a leftover of the ruby heritage.

Personally I prefer a case that strictly tests for true and false over a sloppy if. The last 2 times I entered a new team this lead to an initial discussion but no styleguide that prefers mine or their implementation, at the end both are used across the code, depending on who wrote the particular section. But there are at least no edit wars.

The reason we enforce neither has been the same in both teams: No one had a credo rule for one or the other.

Both teams agreed on the potential pain with slipping values. Though in both teams I have been confident (at the time of the discussion) that there would be no way to non actual bools slipping into the condition.

I even prefer and/2/or/2 to write the conditions rather than &&/2/||/2! Because I consider a logged crash more valuable than a missbehaving condition.

2 Likes

Agreed with case, while if allows you to skip the else clause, this also means that you will inevitably have places where you forget to define the else clause by mistake and have random nils going throughout your codebase that are hard to track.

1 Like

Too bad @dimitarvp’s post was flagged, it was good comedy, but I get it :upside_down_face:

I actually have the contrarian view. For me the fact that only false and nil are falsey is exactly what makes truthiness viable. I otherwise found it incredibly chaotic in other languages. Of course, you have to be judicious about it and expect that everyone involved in a project will be as well, which I suppose is not always possible. I’m used to working in very small teams and pairing a lot so :person_shrugging:

In any event, I only use truthiness if I’m going to do something with the checked value. For example:

if field = get_field(changeset, :field) do
  put_changeset(changeset, :field, "#{field} plus new stuff")
else
  changeset
end

But let’s say I wanted to change the value of a field based on the presence of other another field then I would write it like so:

if get_field(changeset, :field) != nil do
  put_change(changeset, :other_field, "new value")
else
  changeset
end

I dunno, I’ve never personally run into any problems with this. I also just prefer how if reads sometimes. This:

case foo do
  true -> "this"
  false -> "that"
end

always felt a bit weird to me and makes me pause to think there might be a missing clause. Although that is my dynamic-language-oriented brain at play there. I do see the merit in blowing up if you get nil instead of false so perhaps I’m shaky ground here. But again, it’s one of those things where I’ve just never felt the pain.

2 Likes

I can just say that coming from Erlang I definitely prefer true to be the only truthy value and false to be the only falsey value. It is MUCH simpler and consistent in the long run. I’m a KICASS (Keep It Consistent And Simple Stupid) person.

10 Likes