Question

I have the following method which accepts a variable and then displays info from a database:

sub showResult {
    if (@_ == 2) {
        my @results = dbGetResults($_[0]);
        if (@results) {
            foreach (@results) {
                print "$count - $_[1] (ID: $_[0])\n";
            }
        } else {
            print "\n\nNo results found";
        }
   }
}

Everything works fine, except the print line in the foreach loop. This $_ variable still contains the values passed to the method.

Is there anyway to 'force' the new scope of values on $_, or will it always contain the original values?

If there are any good tutorials that explain how the scope of $_ works, that would also be cool!

Thanks

Était-ce utile?

La solution

In Perl, the _ name can refer to a number of different variables:

The common ones are:

$_ the default scalar (set by foreach, map, grep)
@_ the default array  (set by calling a subroutine)

The less common:

%_ the default hash (not used by anything by default)
 _ the default file handle (used by file test operators)
&_ an unused subroutine name
*_ the glob containing all of the above names

Each of these variables can be used independently of the others. In fact, the only way that they are related is that they are all contained within the *_ glob.

Since the sigils vary with arrays and hashes, when accessing an element, you use the bracket characters to determine which variable you are accessing:

$_[0]   # element of @_
$_{...} # element of %_

$$_[0]  # first element of the array reference stored in $_
$_->[0] # same

The for/foreach loop can accept a variable name to use rather than $_, and that might be clearer in your situation:

for my $result (@results) {...}

In general, if your code is longer than a few lines, or nested, you should name the variables rather than relying on the default ones.


Since your question was related more to variable names than scope, I have not discussed the actual scope surrounding the foreach loop, but in general, the following code is equivalent to what you have.

for (my $i = 0; $i < $#results; $i++) {
    local *_ = \$results[$i];
    ...
}

The line local *_ = \$results[$i] installs the $ith element of @results into the scalar slot of the *_ glob, aka $_. At this point $_ contains an alias of the array element. The localization will unwind at the end of the loop. local creates a dynamic scope, so any subroutines called from within the loop will see the new value of $_ unless they also localize it. There is much more detail available about these concepts, but I think they are outside the scope of your question.

Autres conseils

The problem here is that you're using really @_ instead of $_. The foreach loop changes $_, the scalar variable, not @_, which is what you're accessing if you index it by $_[X]. Also, check again the code to see what it is inside @results. If it is an array of arrays or refs, you may need to use the indirect ${$_}[0] or something like that.

As others have pointed out:

  • You're really using @_ and not $_ in your print statement.
  • It's not good to keep stuff in these variables since they're used elsewhere.

Officially, $_ and @_ are global variables and aren't members of any package. You can localize the scope with my $_ although that's probably a really, really bad idea. The problem is that Perl could use them without you even knowing it. It's bad practice to depend upon their values for more than a few lines.

Here's a slight rewrite in your program getting rid of the dependency on @_ and $_ as much as possible:

sub showResults {
    my $foo = shift;    #Or some meaningful name
    my $bar = shift;    #Or some meaningful name

    if (not defined $foo) {
       print "didn't pass two parameters\n";
       return;  #No need to hang around
    }
    if (my @results = dbGetResults($foo)) {
        foreach my $item (@results) {
        ...
    }
}

Some modifications:

  • I used shift to give your two parameters actual names. foo and bar aren't good names, but I couldn't find out what dbGetResults was from, so I couldn't figure out what parameters you were looking for. The @_ is still being used when the parameters are passed, and my shift is depending upon the value of @_, but after the first two lines, I'm free.
  • Since your two parameters have actual names, I can use the if (not defined $bar) to see if both parameters were passed. I also changed this to the negative. This way, if they didn't pass both parameters, you can exit early. This way, your code has one less indent, and you don't have a if structure that takes up your entire subroutine. It makes it easier to understand your code.
  • I used foreach my $item (@results) instead of foreach (@results) and depend upon $_. Again, it's clearer what your program is doing, and you wouldn't have confused $_->[0] with $_[0] (I think that's what you were doing). It would have been obvious you wanted $item->[0].
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top