perl closures and $_

2019-03-23 15:47发布

问题:

One of the first things I try to learn in an unfamiliar programming language is how it handles closures. Their semantics are often intertwined with how the language handles scopes and various other tricky bits so understanding them reveals several other aspects of the language. Plus, closures are a really powerful construct and often times cut down on the amount of boilerplate I have to type. So I was messing around with perl closures and I stumbled upon a little gotcha:

my @closures;
foreach (1..3) {
  # create some closures
  push @closures, sub { say "I will remember $_"; };
}
foreach (@closures) {
  # call the closures to see what they remember
  # the result is not obvious
  &{$_}();
}

When I wrote the above code I was expecting to see

I will remember 1
I will remember 2
I will remember 3

but instead I got I will remember CODE(0x986c1f0).

The above experiment revealed that $_ is very context dependent and if it appears in a closure then it's value is not fixed at the point of the closure's creation. It behaves more like a reference. What other gotchas should I be aware of when creating closures in perl?

回答1:

Closures only close over lexical variables; $_ is normally a global variable. In 5.10 and above, you can say my $_; to have it be lexical in a given scope (though in 5.18 this was retroactively declared to be experimental and subject to change, so better to use some other variable name).

This produces the output you expected:

use strict;
use warnings;
use 5.010;
my @closures;
foreach my $_ (1..3) {
  # create some closures
  push @closures, sub { say "I will remember $_"; };
}
foreach (@closures) {
  # call the closures to see what they remember
  # the result is not obvious
  &{$_}();
}


回答2:

$_ is a global variable and should not be used in closure. Before using it assign this to a lexically scoped variable as shown bewlow. This will produce expected o/p.

#!/usr/bin/perl -w

use strict;
my @closures;


foreach (1..3) {
   my $var = $_;
   push @closures, sub { print "I will remember $var"; };
}

foreach (@closures) {
  $_->();
  print "\n";
}