[Pdx-pm] each within eval

Marvin Humphrey marvin at rectangular.com
Wed Feb 23 09:24:47 PST 2005


Some reference acrobatics I'm doing seem to be causing bizarre behavior 
inside an eval.  I have a $hashref that's ricocheted all over the 
innards of a large object.  There are multiple copies hanging around 
pointing at the same anonymous hash.  That might be where the problem 
originates, though I'm not sure why.

For performance reasons, I'm constructing a tight loop using 
conditionals to build a scalar full of $code to be eval'd.

I can iterate through the key-value pairs in the hash using each, so 
long as I do it inside the main body of the module:

while (my ($k, $v) = each %$hashref) {
	print "$k => $v\n";

But if I put that same each loop inside $code and eval it, the loop 
doesn't execute.  each doesn't iterate, and the assignment expression 
returns 0.  Other code works fine inside the eval, I've got strict and 
warnings turned on, and I'm checking $@.

This works inside the eval:

foreach my $k (keys %$hashref) {
	my $v = $hashref->{$k};
	print "$k => $v\n";

... But that's not as efficient, and this loop may be executed millions 
of times.  I can also unroll and reroll the hash...

my %copy = %$hashref;
while (my ($k, $v) = each %copy) {
	print "$k => $v\n";
... but again, that's wasteful.

When I print Dumper($hashref), it looks normal.

And of course, when I write a sanity check test script to perform the 
same function, it works as expected.  It might take me quite some time 
to write test scripts to isolate the problem.

Any ideas on where I go next to troubleshoot this?


Marvin Humphrey
Rectangular Research

### The relevant code, snipped from the module:

### Build a routine for deriving a result set from an undetermined
### number of productions, each of which may be required, negated, or
### neither, and which must be sorted either by accumulated score or
### by date.
### TODO Is it possible to make this less tortured?
my $routine_part1 = q(
     my $merged = { '' => undef };
     while (my ($doc_num, $score_or_timestamp)
         = each %{ $production->{result_set} })
my $routine_part2 = $self->{-sort_by} eq 'timestamp' ?
     $result_set->{$doc_num} = $score_or_timestamp;)  :
     $result_set->{$doc_num} += $score_or_timestamp;);
$routine_part2 .= q(

if (%$neglist) {
     $routine_part1 .= q(
         next if exists $neglist->{$doc_num};);

if (%$reqlist) {
     $routine_part1 .= q(
         next unless exists $reqlist->{$doc_num};);

if (%$reqlist and $production->{required}) {
     $routine_part1 .= q(
         $merged->{$doc_num} = undef;);
     $routine_part2 .= q(
         foreach (keys %$result_set) {
             delete $result_set->{$_} unless exists $merged->{$_};
         $reqlist = $merged;
         $merged = { '' => undef };);
elsif ($production->{required}) {
     $routine_part2 .= q(
         $reqlist = $production->{result_set};
         ### Make %$reqlist evaluate to true, even if it contains no
         ### docs -- because if you require a term, and it doesn't exist
         ### in any documents, your query shouldn't return anything.
         $reqlist->{''} = undef;);

eval "$routine_part1 $routine_part2";
die $@ if $@;

More information about the Pdx-pm-list mailing list