Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Re: Turning foreach into map?

by tlm (Prior)
on Apr 05, 2005 at 00:19 UTC ( #444830=note: print w/ replies, xml ) Need Help??


in reply to Turning foreach into map?

I have never used map before...

Once you start, you won't stop! You'll wonder how you could program without it.

...and I have read the map function page on http://perldoc.perl.org/functions/map.html, but I don't seem to quite understand.

As a first approximation, you can imagine map as being defined something like this:

sub my_map { my $sub = shift; my @out; for my $element ( @_ ) { push @out, $sub->( $element ); } return @out; }
This function my_map takes a code ref as its first argument, and returns the array consisting of applying that code ref to each one of the remaining arguments. Very useful! When programming we often want all the elements of some array, but transformed or operated on in some way. my_map (just like map) abstracts the essentials of this procedure, so that the only variables left are the actual input array and the operation that you want performed on each element.

You would use my_map like this:

sub square { $_[0] * $_[0] } my @squares = my_map( \&square, 1..3 ); # @squares is now ( 1, 4, 9 )
or if you don't want to bother defining a sub just to feed it to my_map, you can give it a code ref written just for the ocassion:
my @squares = my_map( sub { $_[0]*$_[0] }, 1..3 );
Same thing. In fact, if you have arranged your definitions so that the compiler processes it before it processes the first call to it, you can toss the parens and write
my @squares = my_map sub { $_[0]*$_[0] }, 1..3;
which is beginning to resemble the way one uses Perl's map. Since this is such a handy thing to do, for its map Perl provides some "syntactic sugar" to simplify the writing of such expressions, so that for example you don't need to bother writing "sub" before the code ref. Also, in Perl's map, the code ref doesn't get its argument passed through its @_ array like is the case with my_map's $sub; instead map's the code ref picks up its argument from $_. In other words, if we wanted my_map to mimic Perl's map more closely, we could define it like this:
sub my_map { my $sub = shift; my @out; for ( @_ ) { push @out, $sub->(); # $sub will get its arg from $_ } return @out; }
The only difference between this and the previous definition of my_map is in the first and second lines of the loop: 1) the loop variable is no longer $element, but $_; and 2) $sub is now called without arguments, since it will read its arguments from $_. Now we can write
my @squares = my_map sub { $_ * $_ }, 1..3;
and that's starting to look a lot like the standard
my @squares = map { $_ * $_ } 1..3; # BLOCK form of map

Now, watch this. If you defined a function that operated on $_, e.g.

sub twice { $_ * 2 }
you could just feed it to Perl's map as its first argument:
my @evens = map &twice, 1..10; # EXPR form of map
and you could also feed it to (the second version of) my_map
my @evens = my_map \&twice, 1..10;
Notice that now the only difference that remains between the syntax of the two calls is that with my_map you need to explicitly take the reference to the sub you are passing as the first argument, hence the extra backslash. Can we get rid of this difference too? Yes, by using prototypes. I for one never use prototypes, so I'm not up on all their ins and outs, but for the sake of this illustration
sub my_map (\&@_) { my $sub = shift; my @out; push @out, $sub->() for @_; return @out; }
(Note, aside from the prototype, there is no significant difference between this definition of my_map and the previous one; I just condensed it a little bit.) Now you can write,
my @evens = my_map &twice, 1..10;
OK, this last version of my_map does a limited impersonation of the "expression form" of map. A slight variant could be used to implement an impersonation of the "block form" of map; the only difference is in the prototype, but just to be clear, here it is in full:
sub my_map (&@) { my $sub = shift; my @out; push @out, $sub->() for @_; return @out; }
With this final version of my_map you can write:
my @evens = my_map { $_ * 2 } 1..3;
Note that even though we are using an anonymous code ref, the sub keyword is gone. This is syntactically identical to the "block form" of map.

Alas, my_map is not as versatile as map. It can't do some things that map can do, like this:

my @evens = my_map $_ * 2, 1..10; # splat!
and it can't mimic both the "expression form" and the "block form" of map. But I think that by studying my_map you'll get a pretty good feel for what map is doing. That's the important thing; the other stuff with tweaking the definition of my_map so it's usage better resembles that of map is a bit of a distraction. The important thing is to understand that map takes a function as its first argument, and returns the array obtained from applying that function to the rest of its arguments.

OK, since I've made this far, I might as well tell you, if not the whole story, at least all that I know about map, and the only thing that's left is that both map and my_map have the potential to change their inputs. If, for example, you did this (using the last version of my_map):

my @ints = 1..3; my @evens = my_map { $_ *= 2 } @ints; print "@evens\n"; print "@ints\n"; __END__ 2 3 6 2 3 6
...the input array @ints has been modified by my_map. Same thing with Perl's map, so watch out.

Last thing: you could play the same game with grep:

sub my_grep (&@) { my $sub = shift; my @out; $sub->() && push @out, $_ for @_; return @out; }

the lowliest monk


Comment on Re: Turning foreach into map?
Select or Download Code
Re^2: Turning foreach into map?
by ghenry (Vicar) on Apr 05, 2005 at 07:08 UTC

    Wow! This will take me a while to properly digest, but my first question is, why is:

    my ($url, @list) = @_;

    now:

    my $sub = shift;

    I have seen loads of people do this. Even merlyn now suggests it in another thread that I can't remember, when it's his book I learnt it from ;-)

    Walking the road to enlightenment... I found a penguin and a camel on the way.....
    Fancy a yourname@perl.me.uk? Just ask!!!

      They're both correct; it all depends on what you want to do. In the example I gave, I was re-implementing Perl's built-in map, not your links function. What my code does is to pluck out the first argument from the arguments list, and later process the remaining arguments directly from the arguments list.

      the lowliest monk

      Arguments are passed "by reference" in perl subs, allowing in-place modification. So, you can do things like the following:
      sub double_it { $_[0] *= 2 } my $x = 1; print "\$x is $x\n"; double_it($x); print "\$x is now $x\n";
      and have
      $x is 1 $x is now 2
      When you use:
      my ($url, @list) = @_;
      you're creating copies of the input arguments, so when you modify them you don't have any side-effect outside the function. In your code, these copies are inefficient and not needed, because the code is quite simple and you don't perform in-place modifications. So, you simply grab the first parameter, and use the resulting @_ instead of @list, saving space and time:
      sub links { my $url = shift; # This removes the first element of @_ map { "$url/$_" } @_; # You don't need return :) }
      I also find this construct useful when porting functions into classes. When you transform a plain function in a class/object method, the first parameter that's passed is the class name/reference to object, so if you have a function:
      sub some_func { # ... some stuff? my ($foo, $bar, $baz) = @_; # ... other stuff }
      you can transform it at once:
      sub some_func { my $self = shift; # Maybe do some checks with $self first? # ... some stuff? my ($foo, $bar, $baz) = @_; # ... other stuff }
      in a much cut-and-paste fashion. But do check the code!

      Flavio (perl -e "print(scalar(reverse('ti.xittelop@oivalf')))")

      Don't fool yourself.
Re^2: Turning foreach into map?
by ghenry (Vicar) on Apr 05, 2005 at 09:33 UTC

    OK, I've read all this now, and I have to be honest, I haven't got as far as references yet, only as far as reading perlreftut, but not actually coded anything.

    I'll get back to you ;-)

    Walking the road to enlightenment... I found a penguin and a camel on the way.....
    Fancy a yourname@perl.me.uk? Just ask!!!

      OK, I've read all this now, and I have to be honest, I haven't got as far as references yet, only as far as reading perlreftut, but not actually coded anything.

      References are far more important than map; if I were you I'd put map aside for a bit and focus on becoming very comfortable with refs.

      the lowliest monk

        Point taken.

        Any other good docs to read apart from perlreftut and the Camel?

        Walking the road to enlightenment... I found a penguin and a camel on the way.....
        Fancy a yourname@perl.me.uk? Just ask!!!

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://444830]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (4)
As of 2014-07-14 03:25 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    When choosing user names for websites, I prefer to use:








    Results (254 votes), past polls