2019/07/22
Nasty undefined
tl;dr - We discourage using #<undef>
where generalized boolean is expected. Defining GAUCHE_CHECK_UNDEFINED_TEST
environment variable turns on the warning.
The other day I was shaving yak, and encountered a nasty tick in the wool. Its name is undefined.
Sometimes, a Scheme expression is defined to be evaluated to an unspecified value. It means it can be any value, at the discretion of
the implementation. However, if an ordinary looking value such as #f
is returned, the users may depend on it. Such code has a hazard that it breaks when ported to other implementation.
Partly because of that, many implementations choose to have a special value returned from an expression whose return value is not specified. In Gauche, we use an undefined value, or #<undef>
when
printed. It is just a placeholder indicating the value doesn't (and shoudn't) matter.
A few days ago I was tweaking a precompiler and made a trivial change
in the generation of .sci
(interface) file. The change broke the
precompiler with no obvious cause. I reverted the change and
started gradually introducing the new code step by step. The breakage
was reproducible, but the reason was incomprehensible. For example,
emitting a newline to the file or not made the diffence.
It took a few hours, plus one night sleep, to finally identify the cause.
The closure I was touching had called a procedure at the tail position that returned
#<undef>
. So I assumed its return
value didn't matter. I changed the closure in a way that it
retuned #f
.
However, in other part of the code, the closure was called as something like this:
(or (and (some-condition) (the-closure) #t) (some-other-action))
Since #<undef>
counts as a true value, it used to execute only the
first arm of or
. After the change
(the-closure)
returned #f
, so (some-other-action)
was
also executed.
D'oh.
If #<undef>
implies the value doesn't matter, we shouldn't rely
on it being counted as true. Any code that tests the return
value of the procedure whose value is undefined in the conditional
has a time bomb.
Upon this realization, I added a check in Gauche VM to warn when
#<undef>
appears in the context of boolean test in the
conditionals. Then...
I found a lot of such cases in Gauche code itself.
Typical pitfall is and-let*
forms in which one of
the test expression yields #<undef>
.
And sometimes they were
hard to track down, for the source of #<undef>
may not always
be obvious. (It was one of relatively uncommon occasions when I did
miss static typing.)
The warnings can be very bothering, so it isn't turned on by default.
You can turn it on by defining the environment variable
GAUCHE_CHECK_UNDEFINED_TEST
. This is an experimental
feature and I might change my mind later, but the current plan is
to enable it during unit tests eventually.
Note: There are occasions that #<undef>
is used intentionally;
one of them is to use it as a placeholder indicating the value
isn't given. Such usage is not desirable but tolerated,
and in general those values are
checked with undefined?
. Gauche only warns
when #<undef>
appears as the result of the test expression of
conditionals.
Post a comment