4

Is map 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?

Janick Bernet
  • 20,544
  • 2
  • 29
  • 55
alexanderkuk
  • 1,551
  • 2
  • 12
  • 29
  • 1
    No, but you encourage more people to answer your question. Accepted marks and upvotes are how we say "thanks" here. And more importantly, people in the future will arrive in those questions looking for a solution and will know which one is the correct. This might help http://meta.stackexchange.com/q/5234/160504 – sidyll Oct 02 '11 at 13:40
  • I see. I typed "thank you" in comments before – alexanderkuk Oct 02 '11 at 13:47
  • @alex - aside from benefits that sidyll mentioned: Comment gives someone a nice feeling. Accept gives the answerer 15 rep points (and 2 rep points to you :) – DVK Oct 02 '11 at 14:13
  • @DVK An accepted answer will typically also give some extra upvotes, some nice cred, and some nice green color in your score sheet. =) – TLP Oct 02 '11 at 17:49
  • Also read Book Higher Order Perl, where they talk about this. – aartist Oct 02 '11 at 18:45

3 Answers3

11

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.

Community
  • 1
  • 1
Leon Timmermans
  • 30,029
  • 2
  • 61
  • 110
  • @leon-timmermans, how does the `&@` prototype still fail to allow for a pure-Perl implementation on account of special syntax? – Richard Simões Oct 02 '11 at 20:46
  • 1
    @RichardSimões: It doesn't allow for jokes like `map 2*$_, 1..10`. Note that the first argument is not a block but a rather special expression. – Leon Timmermans Oct 05 '11 at 22:30
5

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;
    }
Eric Strom
  • 39,821
  • 2
  • 80
  • 152
0

My Object::Iterate module is an example of what you are trying to do.

brian d foy
  • 129,424
  • 31
  • 207
  • 592