How is the map function in Perl implemented?
Is m开发者_C百科ap
function in Perl written in Perl? I just can not figure out how to implement it. Here is my attempt:
use Data::Dumper;
sub Map {
my ($function, $sequence) = @_;
my @result;
foreach my $item (@$sequence) {
my $_ = $item;
push @result, $function->($item);
}
return @result
}
my @sample = qw(1 2 3 4 5);
print Dumper Map(sub { $_ * $_ }, \@sample);
print Dumper map({ $_ * $_ } @sample);
$_
in $function
is undefined as it should be, but how map
overcomes this?
map
has some special syntax, so you can't entirely implement it in pure-perl, but this would come pretty close to it (as long as you're using the block form of map):
sub Map(&@) {
my ($function, @sequence) = @_;
my @result;
foreach my $item (@sequence) {
local $_ = $item;
push @result, $function->($item);
}
return @result
}
use Data::Dumper;
my @sample = qw(1 2 3 4 5);
print Dumper Map { $_ * $_ } @sample;
print Dumper map { $_ * $_ } @sample;
$_
being undefined is overcome by using local $_
instead of my $_
. Actually you almost never want to use my $_
(even though you do want to use it on almost all other variables).
Adding the (&@)
prototype allows you not to specify sub
in front of the block. Again, you almost never want to use prototypes but this is a valid use of them.
While the accepted answer implements a map
-like function, it does NOT do it in the way perl would. An important part of for
, foreach
, map
, and grep
is that the $_
they provide to you is always an alias to the values in the argument list. This means that calling something like s/a/b/
in any of those constructs will modify the elements they were called with. This allows you to write things like:
my ($x, $y) = qw(foo bar);
$_ .= '!' for $x, $y;
say "$x $y"; # foo! bar!
map {s/$/!!!/} $x, $y;
say "$x $y"; # foo!!!! bar!!!!
Since in your question, you have asked for Map
to use array references rather than arrays, here is a version that works on array refs that is as close to the builtin map
as you can get in pure Perl.
use 5.010;
use warnings;
use strict;
sub Map (&\@) {
my ($code, $array) = splice @_;
my @return;
push @return, &$code for @$array;
@return
}
my @sample = qw(1 2 3 4 5);
say join ', ' => Map { $_ * $_ } @sample; # 1, 4, 9, 16, 25
say join ', ' => map { $_ * $_ } @sample; # 1, 4, 9, 16, 25
In Map
, the (&\@)
prototype tells perl that the Map
bareword will be parsed with different rules than a usual subroutine. The &
indicates that the first argument will either be a bare block Map {...} NEXT
or it will be a literal code reference Map \&somesub, NEXT
. Note the comma between the arguments in the latter version. The \@
prototype indicates that the next argument will start with @
and will be passed in as an array reference.
Finally, the splice @_
line empties @_
rather than just copying the values out. This is so that the &$code
line will see an empty @_
rather than the args Map
received. The reason for &$code
is that it is the fastest way to call a subroutine, and is as close to the multicall calling style that map
uses as you can get without using C. This calling style is perfectly suited for this usage, since the argument to the block is in $_
, which does not require any stack manipulation.
In the code above, I cheat a little bit and let for
do the work of localizing $_
. This is good for performance, but to see how it works, here is that line rewritten:
for my $i (0 .. $#$array) { # for each index
local *_ = \$$array[$i]; # install alias into $_
push @return, &$code;
}
My Object::Iterate module is an example of what you are trying to do.
精彩评论