Monday, September 28, 2009

Funny Business with the Billion Dollar Mistake

First, null often gets a bad rap, and it deserves what it gets. Unfortunately, it can be quite handy, and it is pretty well understood by the average programmer, so I'm keeping it anyway, at least for now. Fortunately almost no one will use Concrete Mix, so it won't be an expensive mistake. Sidenote: I had to look this up while writing the above.

So, last time I said that null acts a bit strangely. To understand the how and the why, let's look at some code, and see how we would typecheck it:

main()
{
  var n = null;
  n.Foo();
  
  var m = null;
  m = new Bar();
  m.Bleck();
  
  return 0;
}

Some totally useless code, but it will prove illustrative, so bear with me. First, let's assume we're playing with the iterative algorithm I've been harping on about. So, after iterating to a fixed point, what does the typeset of n contain? Let's say that it contains nothing (that is, null indicates no type, and not an empty type, like void might indicate): {}. Then, should the call n.Foo() be deemed correct by the typechecker?

It turns out that classifying this call as incorrect has some unforseen consequences. I noticed the problem first when playing with a simple singly-linked list implementation. Consider the following code for a naive Length method:

class List
{
  private var m_Head;
  public List() {...};
  public Append(element) {...};
  public Length()
  {
    var count = 0;
    var current = this.Head;
    
    while(current is not null)
    {
      count += 1;
      current = current.Next();
    }
    return count;
  }
}

(Syntax note: as operator== can be overridden, Mix offers the is and is not operators for referential equality and inequality, respectively).

This code is a tiny bit more useful, though it's only a sketch. I'm sure you get the idea. The point is that, if we call Length on a list that is always empty, and we treat null as described above, we have a problem: the typeset of current is always empty, and so the call current.Next() would be flagged as incorrect by the typechecker. Now, you might suggest we perform some kind of nullness analysis and thereby determine that the body of the while loop cannot be executed, but this brings its own issues, so let's leave it alone for now.

So maybe our initial decision was wrong, and null should indicate the empty type. But this is no good, for consider m in the first code listing: its typeset would be something like {Null[]; Bar[...]}, and no method call on that typeset could ever succeed, as Null presumably implements no methods.

So it seems like the only solution (until we take a harder look at nullness analysis) is to classify calls on the empty typeset as correct. And in fact, this doesn't violate our definition of correctness, because the invocation n.Foo() will raise a null pointer exception, not a "method does not exist" exception.

No comments:

Post a Comment