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