Home > Software engineering >  You can't assign PERL $_ to a hash in a foreach statement
You can't assign PERL $_ to a hash in a foreach statement

Time:04-09

I have tried everything I can think of (plus a tonne of random experiments) to assign $_ within the foreach statement to a hash. I know that you can assign it after the foreach line, but I have a very particular code limitation which doesn't allow me to do this.

#!/usr/bin/perl -s

use strict;
use Data::Dumper;

my @x = qw(a c e g);
my $var = {'a'=>"z", 'b'=>"y", 'c'=>"x", 'd'=>"w", 'e'=>"v", 'f'=>"u", 'g'=>"t", 'h'=>"s"};
my $y = "e";
foreach $var->{$y} (@x){
    print "$y = $var->{$y}\n";
    $y = $var->{$y};
}

What I think is happening is the perl compiler cannot handle '{' and '}' characters in the position where it is looking for $VAR to assign $_ to. When I run the code not only do I get an error about the foreach line, but I also get a syntax error about the print line where there is no syntax error.

syntax error at ./foreach2var.pl line 9, near "$var->"
syntax error at ./foreach2var.pl line 12, near "}"
Execution of ./foreach2var.pl aborted due to compilation errors.

Is there any way to assign $_ within the foreach statement directly to a hash reference rather than assigning it with $var->{$y} = $_; on the following line?

I am looking for the following output

e = a
a = c
c = e
e = g

Also, I am aware that the following works, but again, I have a particular code limitation which means I have to be able to handle foreach statements

#!/usr/bin/perl -s

use strict;
use Data::Dumper;

my @x = qw(a c e g);
my $var = {'a'=>"z", 'b'=>"y", 'c'=>"x", 'd'=>"w", 'e'=>"v", 'f'=>"u", 'g'=>"t", 'h'=>"s"};
my $y = "e";
while($var->{$y} = shift @x){
    print "$y = $var->{$y}\n";
    $y = $var->{$y};
}

CodePudding user response:

From Foreach Loops in perlsyn

The foreach loop iterates over a normal list value and sets the scalar variable VAR to be each element of the list in turn.

Thus, it needs a variable and the dereferenced computed-on-the-fly value you'd like to assign to isn't even an lvalue, much less a variable.

How strict is this "variable" requirment? The rest of that paragraph goes

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.

So we do need a variable there. The requirement is purely syntactical though.

I'm not sure what precisely you need to do and what your restrictions are but

for (@x) { 
    $var->{$y} = $_;
    ...
}

does indeed assign as desired, as observed in the question.

Having it assigned as a topicalizer, for $v (@ary) { }, does have the extra property of localizing $v within the loop, as quoted above from docs, but you don't seem to need that part.


One can localize parts of a complex structure. A command-line program

perl -Mstrict -wE'
    my $hr = { k => 2 };  say $hr->{k}; 
    SAVE: { local $hr->{k} = 7; say $hr->{k} }; 
    say $hr->{k}'

prints lines 2, 7, 2.

CodePudding user response:

No. As per the documentation, it must be a variable.

It really doesn't make sense for it to be anything else because the variable's value is restored at the end of the loop.

$_ = "abc";
say;

for (qw( def ghi )) {
   say;
}

say;

Output:

abc
def
ghi
abc

You can get the desired output from each of the following:

my $prev = "e";
while ( @x ) {
   my $this = shift( @x );
   say "$prev = $this";
   $prev = $this;
}
my $prev = "e";
for my $this ( @x ) {
   say "$prev = $this";
   $prev = $this;
}
my $this = "e";
while ( @x ) {
   ( my $prev, $this ) = ( $this, shift( @x ) );
   say "$prev = $this";
}
unshift @x, "e";
for my $i ( 1 .. $#x ) {
   say "$x[ $i - 1 ] = $x[ $i ]";
}
  • Related