0

Using shift within eval string that's inside a sub isn't working:

Example 1 (using eval):

grab_variable($variable1,$variable2,$variable3);
print $variable1.$variable2.$variable3; #should print: 123

sub grab_variable {
    my $number_of_parameters = @_;
    my $command1;
    my $command2;
    for (my $i = 1; $i <= $number_of_parameters; $i++) {
        $command1.= "\$RefVariable".$i." = \\shift;\n";
            #Generates string like:
                #$RefVariable1 = \shift;
                #$RefVariable2 = \shift;
                #...
    }
    eval $command1;
    for (my $i = 1; $i <= $number_of_parameters; $i++) {
        $command2.= "\$\{\$RefVariable".$i."\} = ".$i.";\n";
            #Generates string like:
                #${$RefVariable1} = 1;
                #${$RefVariable2} = 2;
                #...
    }
    eval $command2;
}

Example 2 (direct code):

grab_variable($variable1,$variable2,$variable3);
print $variable1.$variable2.$variable3; #Prints: 123

sub grab_variable {
    $RefVariable1 = \shift;
    $RefVariable2 = \shift;
    $RefVariable3 = \shift;
    ${$RefVariable1} = 1;
    ${$RefVariable2} = 2;
    ${$RefVariable3} = 3;
}

Example 1 is dynamic and achieves a solution to my real (not simplified) problem. How could I make \shift work within the eval code string?

Specifically, I'm trying to make a function (subroutine) that accepts any number of variables (as arguments) and get passed as references (not values) because it should modify them.

Omar
  • 6,681
  • 5
  • 21
  • 36
  • 2
    `$grab_variable($var)` is invalid syntax. – simbabque Feb 07 '17 at 16:09
  • 1
    `"\$RefVariable" . $i` is a [code smell](http://stackoverflow.com/questions/1549685/how-can-i-use-a-variable-as-a-variable-name-in-perl). Isn't there a way to do this with arrays? – mob Feb 07 '17 at 16:09
  • Also, if you're just trying to do pass-by-reference, the contents of `@_` are already aliases to the arguments passed to the subroutine. `$ref=\shift; $$ref="foo";` is equivalent to `$_[0]="foo"` – mob Feb 07 '17 at 16:12
  • @mob `$$ref="foo"`. And, no, they aren't equivalent, one modifies the caller's variable and one doesn't. – ysth Feb 07 '17 at 16:12
  • @ysth - good catch. But `$$ref=...` does modify the caller's variable. – mob Feb 07 '17 at 16:18
  • Can you explain more generally what you're trying to do? There's almost certainly a better way to do it. – ThisSuitIsBlackNot Feb 07 '17 at 16:28
  • @simbabque I fixed it. Thanks – Omar Feb 07 '17 at 17:33
  • @ThisSuitIsBlackNot I'm trying to make a function that accepts any number of variables and get passed as references (not values) because it should modify them. – Omar Feb 07 '17 at 17:34
  • 2
    @Omar `sub modify_args { $_ = 'foo' for @_ }` is much simpler. To modify subroutine arguments, simply access `@_` directly, e.g. `$_[0] = 42;`. – ThisSuitIsBlackNot Feb 07 '17 at 17:36
  • @ThisSuitIsBlackNot Amazing. `$_[0]` acts as a **variable** *(not reference)* interpolating the variable's value in a ***calling context*** **(e.g.** `print $_[0];` prints first argument's value (*not its referencing value somewhat like* `SCALAR(0x10ef1b0)`) without the need to type `print ${$_[0]}` which doesn't work **)**. While it acts as a ***reference*** to the first argument modifying the variable's value in a modifying context **(e.g**. `$_[0] = 4;` **)**. – Omar Feb 07 '17 at 18:25
  • 1
    It's called aliasing. An alias is just another name for the same thing. `$_[0]` is an alias for the first subroutine argument. See [Alias vs Reference](http://www.perlmonks.org/?node_id=976523) on PerlMonks. – ThisSuitIsBlackNot Feb 07 '17 at 18:33

2 Answers2

3

In subs, shift is short for shift(@_).

Outside of subs, shift is short for shift(@ARGV).

This applies in code passed to eval EXPR too.

Replace

$command1.= "\$RefVariable".$i." = \\shift;\n";

with

$command1.= "\$RefVariable".$i." = \\shift(\@_);\n";

You should always use use strict; use warnings qw( all );. Your code does everything you shouldn't do, and this would catch that.

I'm not sure what you are trying to achieve, but have you considered

sub set_variables {
   for my $i (0..$#_) {
      $_[$i] = $i;        # Changing the elements of @_ changes them in the caller.
   }
}

set_variables(my ($var1, $var2, $var3));

say "$var1 $var2 $var3";

or

sub grab_variables { return \@_; }

my $ref_variables = grab_variables(my ($var1, $var2, $var3));

for my $i (0..$#$ref_variables) {
   $ref_variables->[$i] = $i;
}

say "$var1 $var2 $var3";
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Yes I'm too close to understanding why `shift` doesn't work within `eval EXPR`. What does `shift` stands for within `eval EXPR`? It seems it doesn't stand for `shift(@_)` within eval. – Omar Feb 07 '17 at 18:32
  • 1
    Your code isn't in a sub, so `shift` is short for `shift(@ARGV)`. – ikegami Feb 07 '17 at 18:35
  • Thanks a lot. Totally explained why `shift` doesn't work. – Omar Feb 07 '17 at 18:36
2

Perl already does pass-by-alias to subroutines, which is even simpler than pass-by-reference. The contents of @_ are aliases to the inputs to the subroutine; modifying $_[$i] modifies the i-th values passed to the sub.

sub grab_variables {
    for my $i (0 .. $#_) {
        $_[$i] = $i+1;
    }
}
my ($variable1,$variable2,$variable3);
grab_variable($variable1,$variable2,$variable3);
print $variable1.$variable2.$variable3;           #Prints: 123
mob
  • 117,087
  • 18
  • 149
  • 283