Reputation: 1285
When I run a simple bit of code like this:
my @arr=(1..5);
my $x;
foreach $x (@arr) {
$x+=10;
}
print "@arr";
The result is "11 12 13 14 15" because $x "becomes" each element in the @arr array in the foreach. Well enough.
But here's my thing... not so much a problem (the solution is easy, but inelegant, and I want my perl to be as elegant as possible).
I wrote a tie module for dealing with COBOL data. It takes a copybook, parses the fields, and then attaches that to a scalar/string so that access to/from the tied hash will return/set values in the string. It works wonderfully.
my %h,$rec;
my $cb=<<END;
01 CH-RECORD.
05 JOB-NUM PIC X.
05 FILLER PIC X(76).
05 REC-TYPE PIC X(2).
END
tie %h, 'COBOLDataTie',$cb,\$rec; #tie the hash to the record via the copybook
From there, I can move a COBOL record to $rec and access the COBOL fields with the %h hash.
Again, this works perfectly. But the problem comes when I want to iterate over, say, an array of COBOL records. So if after the above code I had something akin to:
foreach $rec (@arr) {
print "Job is ",$h{'JOB-NUM'},"\n";
}
it won't work because the foreach actually changes the location of $rec, which breaks the tie on it. I end up having to do something like this:
foreach (@arr) {
$rec=$_;
print "Job is ",$h{'JOB-NUM'},"\n";
}
Is there any way I can do the "foreach $rec (@arr)" and not break my tied hash?
(And before anyone says, yes I know this begs for a nice object-oriented solution... some day I'll get to that; I just have to find some time first)
EPILOGUE: I revised the TieHash code to, instead of pointing to an external record, it intercepts "special" keys for the hash, among which is 'record'. So when I assign a record string to $h{'record'} it's the same as loading $rec in the example above. This is a far better solution, more self-contained. It also exposes a more OOP-like interface.
Upvotes: 2
Views: 474
Reputation: 1285
It seems like the best way is to do something like:
for (my $i=0;($rec=$arr[$i], $i<@arr);$i++) {
Not exactly the elegance I was hoping for, but it seems to work.
Upvotes: 0
Reputation: 118595
This is a subtle point and easy to overlook in the documentation, but the loop variable in a foreach
variable is always a new variable, and has nothing to do with any lexical or package variables with the same name anywhere else in the program.
From perlsyn
:
Foreach Loops
The "foreach" loop iterates over a normal list value and sets the variable VAR to be each element of the list in turn. If the variable is preceded with the keyword "my", then it is lexically scoped, and is therefore visible only within the loop. Otherwise, the variable is implicitly local to the loop and regains its former value upon exiting the loop. If the variable was previously declared with "my", it uses that variable instead of the global one, but it's still localized to the loop. This implicit localization occurs only in a "foreach" loop.
(emphasis added). That is, the $rec
in line 3 of this little script has nothing to do with the $rec
declared in line 1
1: my $rec = 'foo';
2: print $rec; # 'foo'
3: foreach $rec (@some_list) {
4: print $rec; # something else
5: }
6: print $rec; # 'foo' again
So if you want to use \$rec
to influence behavior of your tied hash (though there are certainly other ways to do it), then you are doing the right thing to use a different loop variable and assign $rec
to it inside the loop.
Upvotes: 2
Reputation: 385655
The interface you decided to create is "assign to $rec
, then access fields via %h
". As such, that's exactly what you need to do.
for (@arr) {
$rec = $_;
print "Job is $h{'JOB-NUM'}\n";
}
Sure it looks weird, but that's beause it is weird. This would make more reasonable:
for (@arr) {
my $h = parse($cb, $_);
print "Job is $h->{'JOB-NUM'}\n";
}
You can even do it with minimal changes:
sub parse {
my ($cb, $rec) = @_;
tie my %h, 'COBOLDataTie', $cb, \$rec;
return \%h;
}
Upvotes: 2