false or nil, which is the better indicator?

I find that it’s the minutia at the lowest level of my frameworks that makes the difference between an elegant well executing system and a bug farm that causes constant pain and suffering. One of the design decisions I struggle with is “should I return nil or ‘false'”.

local func1 = function(condition)
  if condition then
    return true
  end

  return nil
end

local func2 = function(condition)
  if condition then
    return true
  end
  return false
end

if func1(true) then print("Hello, World") end;
if not func1(false) then print("no hope") end;
if not func2(false) then print ("false profit") end;

In these contrived cases, the consequence of using ‘nil’ or ‘false’ does not make a difference. When I perform the ‘if not’ test at the end, both ‘nil’ and ‘false’ will evaluate to ‘false’ and the statement is printed.

I would say that in 99% of the cases in my code, this is perfectly fine, and I can just use either nil or ‘false’ without worrying about the difference.

When you’re capturing return values, and storing the results in a table, then things get much more interesting.

local func1 = function()
  return nil, nil, false
end

local func2 = function()
  return false, "message2", nil
end

local func3 = function()
  return nil, "message3", false, true
end

local func4 = function()
  return nil, "message3", nil, false, true
end

res1 = {func1()};
res2 = {func2()};
res3 = {func3()};
res4 = {func4()};

The key ‘funky’ thing here has to do with how Lua stores ‘nil’ values, and the dual nature of the table structure as both an array and a dictionary. In this case, I a table is constructed by simply wrapping the return value of each of the functions. You might do something like this if you were writing a scheduler which executes a function and captures the returned values in a table, or some similar situation.

print("Size Res1: ", #res1);
>Size Res1: 0

print("Size Res2: ", #res2);
>Size Res2: 2

print("Size Res3: ", #res3);
>Size Res3: 4

print("Size Res4: ", #res4);
>Size Res4: 0

What?

Yah, it’s kind of funky. The challenge is with using the ‘#’ – __len operator is the heuristic to determine the size of a table is kind of funky. As you can see, if there are any gaps in values, as in cases 1, and 4, the thing will be considered to be a dictionary, instead of an array, and thus the length will be reported as zero. On the other hand, if the ‘nil’ is at the beginning of the sequence, the table will still be considered to be an array. You see this with res3. Of course, the fact that it also counts the ‘nil’ value at the beginning as part of the size, is not a good thing.

In the case of res2, you get a size of 2 as there’s no gap that includes a nil, and the trailing ‘nil’ value is not counted.

This seems very wacky behavior. Certainly not reasonably deterministic from a consumer’s perspective. But, there it is.

What this indicates to me is a couple of things. First of all, if you’re going to do something like stuff the return values of a function into a table in this way, you need to be very careful. If you’re then going to iterate those results using the following mechanism:

for i=1,#res do
  somefunc(res[i]);
end

Be extremely careful! Depending on what’s being returned, you may not get the results you’re expecting.

The other thing this little exercise indicates to me is that I want to be very deliberate about how I choose my return values. Instead of being fairly loose, flipping a coin each time to decide whether I will return ‘nil’ or ‘false’, 99% of the time, I’m now going to return ‘false’ instead of nil, unless I have a really good reason for returning ‘nil’.

This will become an issue when I’ve got class constructors, and I immediately start trying to exercise code, but on the other hand, I should be checking the return value of a class constructor anyway, and that check will work properly if I use ‘false’ or ‘nil’, so I’ll favor ‘false’.

And there you have it. The minutia that kills otherwise awesome code. If you pay attention to it early on, you avoid tremendous headaches later.

Advertisements


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s