2

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

Wolf
  • 9,679
  • 7
  • 62
  • 108
skeniver
  • 2,647
  • 6
  • 28
  • 34
  • 1
    General rule of thumb is to never use default scalar unless absolutely necessary. `foreach my $result (@results)` is infinitely better from code maintainability/readability compared to `foreach (@results)`. Just because Perl is very permissive as far as characters typed vs. readability tradeoff, doesn't mean you shouldn't always err on readability side. Always code as if the next developer maintaining your code is a raging psychotic who knows where you live :) – DVK Sep 21 '11 at 03:44
  • Actually, this question is not about the scope of `$_`, it results from misunderstanding about Perl naming conventions. – Wolf Feb 24 '17 at 08:37

3 Answers3

9

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.

Diego Sevilla
  • 28,636
  • 4
  • 59
  • 87
  • 3
    `$$_[0]` is perfectly fine. And is easier to type. :) – tchrist Sep 20 '11 at 20:20
  • @ikegami, @tchrist, nice, noted. It is still not clear what's in `@results`, though. – Diego Sevilla Sep 20 '11 at 20:43
  • 2
    @tchrist - sorry but I must disagree. `$_->` clearly shows you are de-referencing at first glance (and is a universal notation as far as x-of-x-of-x nested dereferencing). `$$_[0]` is MUCH harder to read, e.g. you can easily misread it as `$_[0]`; AND doesn't work as a notation for further dereferening (my usual litmus tests are looking through unfamiliar code half-asleep at 2am in the morning with CEO yelling at you to fix a production problem YESTERDAY; and having the code maintained by a junior developer). Code readablity-wise it's the $$ syntax is not nearly as fine as an arrow. – DVK Sep 21 '11 at 03:38
  • @DVK => any time that you have sigils back to back (`$$`, `@$`, `%$`), it shows you are dereferencing at first glance. the `$$_[0]` syntax works fine for further dereferencing: `$$_[0]{this}[0]{that}`. the `$$_[0]` syntax is also analogous to the expanded `${$_}[0]` syntax, and the `@$_[1,2]` slice syntax. when teaching a developer, all you have to tell them is that for any working syntax using the `@array` variable, you can do a textual replace of `array` with `$array` and the new code will be using references correctly. in other words, `'$array'` is the complete identifier. – Eric Strom Sep 21 '11 at 07:48
  • 1
    @Eric - You're arguing the wrong point. I agree with 100% of what you said - I know that from language design/efficiency point `$*` double sygiling is a good and logical thing. Doesn't make it more readable when instant readability/clarity is **at premium**. I don't think anyone did a study but I bet MORE people make assorted reading errors when quickly scanning through `$$_[0]{this}[0]{that}` vs. `$arg->{this}->[0]->{that}` in cognitively impaired condition. Just look at David W.'s answer code vs. OP's – DVK Sep 21 '11 at 08:26
4

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.

Eric Strom
  • 39,821
  • 2
  • 80
  • 152
2

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].
David W.
  • 105,218
  • 39
  • 216
  • 337