perlmeditation
ambrus
<p>
This meditation
is a story about how the aliasing semantics of perl has
caused me a hard-to-find bug.
This is not meant to prove that aliasing is useless,
I know how useful it can be in other circumstances.
<p>
Suppose I have an array of intermediate results
(the numbers below are for illustration only):
<code>
my @root = (1022, 2);
</code>
While debugging a program, I decided to dump the array at one
point of the program. However, I thought that the array
could get too large, so I decided to print only the
first eight elements. I knew that all the elements of the
array were defined, so I decided to do this:
<code>
$verbose and warn "found " . @root . " root nodes: " . join(" ", grep { defined } @root[0 .. 7]);
</code>
Unexpectedly, this caused a different error in the program,
resulting in these messages:
<readmore>
<code>
Use of uninitialized value in hash element at ./rock-cut line 163.
Can't use an undefined value as an ARRAY reference at ./rock-cut line 171.
</code>
It took me quite a lot of time to figure out what was wrong.
The program wasn't running very fast, so I had to wait
for its completion after each change. I didn't try to
run it with DB, because that would make the program
about 4 times slower.
</readmore>
<p>
After a while, I figured out that if I change the line to this,
the bug disappears:
<code>
$verbose and warn "found " . @root . " root nodes: " . join(" ", @root[0 .. (@root < 8 ? @root : 8) - 1]);
</code>
<p>
I was very surprised, because I wouldn't have thought
this change could make any difference.
But the fact is that the first version has changed the array.
<readmore>
<p>
Recall that <code>@root</code> had two elements.
This expression
<code>
grep { defined } @root[0 .. 7]
</code>
has made the elements <code>@root[2 .. 7]</code> spring
into <code>exists</code>ance, thus the array has
grown to 8 long.
This has caused a problem later, when I iterated through
<code>@root</code> and tried to use the undefined elements
as a hash key.
</readmore>
<p>
Now just by saying <code>() = @root[0 .. 7]</code>
does not change the length of the array,
it just returns undefined values where the elements
do not exist. So what's happened here?
<p>
Well, the answer is that <code>grep</code> wants to alias
the elements of the list to <code>$_</code> so perl
must create a scalar for them.
<readmore>
<p>
The same thing can happen with an array element instead
of a range, or a hash slice or element.
It does not happen with the <code>print</code> operator
instead of <code>grep</code>
It can also happen with user-defined subroutine instead of print.
</readmore>
<p>
There are still issues here I don't understand.
I'd be glad if someone could shed some light on how come
<code>$a[1]</code> does not exist in the following test
program:
<readmore>
<code>
#!perl
use warnings; no warnings "uninitialized";
use Dumpvalue; $D = Dumpvalue->new;
sub f { print $_[0]; } sub g { $_[0] = 5; }
f($a[1]); f(@a[2, 3]); g($a[5]); g(@a[6, 7]);
print $a[9]; print @a[10, 11]; () = grep 0, $a[13]; () = grep 0, @a[14, 15];
$t = $a[17]; @t = @a[18, 19];
f($h{1}); f(@h{2, 3}); g($h{5}); g(@h{6, 7});
print $h{9}; print $h{10, 11}; () = grep 0, $h{13}; () = grep 0, @h{14, 15};
$t = $h{17}; @t = @h{18, 19};
$D->dumpValues(\@a, \%h);
__END__
</code>
Output:
<code>
0 ARRAY(0x81495c0)
0 empty slot
1 empty slot
2 undef
3 undef
4 empty slot
5 5
6 5
7 undef
8 empty slot
9 empty slot
10 empty slot
11 empty slot
12 empty slot
13 undef
14 undef
15 undef
1 HASH(0x8192464)
13 => undef
14 => undef
15 => undef
2 => undef
3 => undef
5 => 5
6 => 5
7 => undef
</code>
</readmore>
<p>
<b>Updates:</b> changed code from one-liner to perl -x script,
per suggestion of [id://194920].
Also added some more readmore tags now that this is frontpaged.
<p>
Let me note that I think this is <i>not autovivification</i>,
and I tried to explain why in [id://456009|a reply] although
my explanation isn't very clear.