Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

When would you choose foreach instead of map?

by jpfarmer (Pilgrim)
on May 20, 2004 at 19:31 UTC ( #355053=perlquestion: print w/replies, xml ) Need Help??

jpfarmer has asked for the wisdom of the Perl Monks concerning the following question:

Somewhat recently, I discovered that when you modify the scalar variable allocated inside of a foreach loop, you can modify the array. While I don't have any trouble finding uses for this, I'm puzzled why it would be used instead of map.

For example, the following operations produce identical output:

@array = qw/1 2 3 4 5/; foreach $item (@array){ $item *= 2; } print join('-', @array), "\n"; @array = qw/1 2 3 4 5/; @array = map {$_ *= 2} @array; print join('-', @array), "\n";

Is there a reason to choose one operator over the other besides personal preference?

Replies are listed 'Best First'.
Re: When would you choose foreach instead of map?
by bart (Canon) on May 20, 2004 at 19:39 UTC
    You can do the same inside a map block. foreach and map act the same in this regard. The technical term is that the loop variable, $_ for map and by default for foreach, is an "alias" to the original item. Modify the alias, and you modify the original.

    When would I use map? When I want to make a copy. Otherwise, I'd let other considerations take precedence. I prefer not to use map in void context, then I use foreach — as a matter of style only, there's no real technical reason to dismiss map. I use map to calculate a function value, either in scalar (count of items) or list context.

Re: When would you choose foreach instead of map?
by dragonchild (Archbishop) on May 20, 2004 at 19:48 UTC
    map is a list-level operator. foreach is a scalar-level operator. That they can often be rewritten in terms of each other is neat, but not much more.

    I use map when I want to transform one list into another list. So, in your example, I would use map to transform the list.

    I use foreach when I want to do complex logic with each item separately. Now, I can use map for nearly every instance I use foreach, but it would require me to think more. As a Perl developer, I'm constitutionally lazy, so I'd rather not think more than I have to. So, I don't. :-)

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

    I shouldn't have to say this, but any code, unless otherwise stated, is untested

Re: When would you choose foreach instead of map?
by Roy Johnson (Monsignor) on May 20, 2004 at 20:15 UTC
    Let's look at this example you provided:
    @array = map {$_ *= 2} @array;
    You're taking @array, changing each value in it, and then assigning it to @array. One of those equals signs is redundant. (If it's not clear why, see below. See below anyway.)

    That is a good way to highlight what the others have already said: if you're making a new array, you'd use map like so:

    @a2 = map { $_ * 2 } @array;
    If you just want to change @array itself, your best fit is foreach (as you used it in your other example), although you can use map in a void context to do the same thing:
    map { $_ *= 2 } @array;

    The PerlMonk tr/// Advocate
      This is all good, but I would say that "end weight" should be the deciding factor. In this case (and probably in most cases), the source of the list is very simple (just an array) so it makes more sense to specify the list first, and then follow it with the block of code acting on it: you use foreach.

      In cases where the source of the list you're acting on is more complicated, you might be able to improve clarity by switching this around and using map to put the code block first, followed by the list.

      (By the way, did you know that map has now been optimized for use in void context? It used to be you'd take a performance hit for using map instead of foreach, but that's not true anymore, for the latest versions of perl.)

Re: When would you choose foreach instead of map?
by dvergin (Monsignor) on May 20, 2004 at 20:06 UTC

    And of course if you wanted to alter the array in place (not creating a new one) and also wanted to do it in one succinct line (as your map does), you could just say:

    $_ *= 2 for @array;

    ------------------------------------------------------------
    "Perl is a mess and that's good because the
    problem space is also a mess.
    " - Larry Wall

Re: When would you choose foreach instead of map?
by Limbic~Region (Chancellor) on May 20, 2004 at 20:56 UTC
    jpfarmer,
    I was confused by map for the longest time until I tried to explain it to someone else. Basically the block of a map statement is like an anonymous sub that acts on each item in the list one item at a time. Unlike a real sub, you can not specify the return value with return. The return value is the last expression evaluated. If you need to add ;$_ to get it to do the right thing, then you probably want a for loop instead. I believe when I came to this epiphany in the CB, tye had a few more good examples that I forget now.

    Cheers - L~R

Re: When would you choose foreach instead of map?
by Abigail-II (Bishop) on May 21, 2004 at 00:22 UTC
    In this case, there isn't a technical reason to use one over the other. In both cases, you are interested in the side-effect. So, in such case, I use map if I want to put focus on the operation, and I use foreach if I want to put focus on the data being operated on. Unless I anticipate a possible change in the code in which there will be a clear preference to use one over the other - in that case, I use that one.

    Abigail

Re: When would you choose foreach instead of map?
by Plankton (Vicar) on May 20, 2004 at 21:06 UTC
    I use foreach when I want my code to be more readable.

    Plankton: 1% Evil, 99% Hot Gas.
Re: When would you choose foreach instead of map?
by bl0rf (Pilgrim) on May 20, 2004 at 22:33 UTC
    map is fairly hideous and foreach looks much nicer. foreach also adds to the linguistic feel of Perl ( the smooth flow ) because it is intuitively understandable and shows off the benefits of arrays. Of course map has its own uses but I think that for simple cases foreach clutters the visual space much less...

      Sorry, but I just cannot agree, or even let this lay unchallenged. map and foreach are indeed different operations, as others have pointed out to great clarity. And the reason they are both in the language is because of their differences, not their similarities.

      map is largely key to the "Schwartzian Transform". It not only produces a list as a side-effect (which foreach doesn't), it can produce very elaborate transformations of the input in the process. map is not hideous simply because someone doesn't understand it as well as foreach. Indeed, for those of us with a Lisp background, it's immediatly intuitive.

      --rjray

        It not only produces a list as a side-effect (which foreach doesn't), ...

        What on earth are you smoking?!? The entire point of map is to produce a list. The list production isn't a side-effect ... it's the whole effect. Modifying the original list is a side-effect.

        ------
        We are the carpenters and bricklayers of the Information Age.

        Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

        I shouldn't have to say this, but any code, unless otherwise stated, is untested

Re: When would you choose foreach instead of map?
by dimar (Curate) on May 21, 2004 at 01:11 UTC

    This whole thread can be summarized in one sentence: The single non-preference-based difference is that 'map' produces the side-effect of assigning to an 'output array' whereas 'foreach' does not. Everything else is personal preference.

    TMTOWTDI Strikes again: (!rant warning!)

    This thread perfectly demonstrates TMTOWTDI, and how it relates to "personal preference". In fact, what perl-folks like to call "TMTOWTDI", I call "The Law of Personal Preference". Its allegories include:

    The number one determining factor to influence code-reuse is personal preference. *Every* reason to choose programming option 'foo' over programming option 'bar' is based on personal preference.
    In fact, to ask whether 'foo' or 'bar' is distinguishable outside the realm of personal preference is nearly *certain* to give you emotionally-influenced answers that are based *substantially* on personal preference.

    Proof: Rephrase the original question according to the following template:

    Is it possible to generate the result 'X' in such a way that I can automatically deduce the method that was used to generate that result (without looking at the source code) by evaluating one or more quantifiable measures of fitness?
    ... in other words, make up a little 'Turing test' ...
    Find a person who can look at the output of a script and determine whether the output came from 'map' or from 'foreach'. They can evaluate everything except the source code itself. (eg, quantifiable measures of fitness, such as how long it takes the script to run, or complile, or whether the output meets a specification, etc. ). Hold all other things equal.
    If your 'quantifiable measure of fitness' is *specifically* how many lines of code it takes to do something (which is a valid measure in itself). Then just look at the different ways of doing it and pick which one seems best for you. Absent an indication that one option is inherently less secure, more buggy, less performant, more likely to be deprecated, (etc etc ad nasuem) than the others, there is no reason why you should not use one over the other (other than ... you guessed it ... personal preference).

    ### TMTOWTDI : ### - SnippetID: tsid="20040520_1649_06239" ### description: multiple ways to loop and munge an array ### details: | ### The following snippet shows a handful of ### alternate coding styles for looping and ### munging an array. use strict; use warnings; my (@array, @outcopy) = (); @array = qw(1 2 3 4 5); ### NOTE: we *dont* care about making an 'output copy' of the array $_ *= 2 foreach @array; print "@array"; print "\n............. \n"; $_ *= 2 for @array; print "@array"; print "\n--------------\n"; map {$_ *= 2 } @array; print "@array"; print "\n............. \n"; for (@array) {$_ *= 2 }; print "@array"; print "\n--------------\n"; foreach (@array) {$_ *= 2 }; print "@array"; print "\n............. \n"; ### NOTE: here we DO care about making an 'output copy' ### notice that both produce same effect, but foreach ### requires additional code because it ### does not produce the side-effect @outcopy = map {$_ *= 2 } @array; print "@outcopy"; print "\n--------------\n"; @outcopy = (); ###<-- additional line foreach (@array) {push @outcopy, $_ *= 2 }; print "@outcopy"; print "\n............. \n";
      Is it possible to generate the result 'X' in such a way that I can automatically deduce the method that was used to generate that result (without looking at the source code) by evaluating one or more quantifiable measures of fitness?

      Where 'X' is the sum of the numbers 1 .. 1_000_000.

      p:\test>x1 500000500000 p:\test>x2 500000500000

      The programs (in no particular order).

      map{ $sum += $_ } 1 .. 1000000; print $sum;
      and
      $sum += $_ for 1 .. 1000000; print $sum;

      External metric: One of them uses 3MB, the other 96MB :)


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail
        I don't know how to check for memory usage (rather than the blunt method of watching the task manager) but here are some speed tests. It appears from this test that they are very simlar in speed for this summing operation. For is the winner but i'm not sure its a noticable portion until you climb into the severl hundered thousand range.
        use strict; use warnings; use Benchmark qw(:all) ; my $to = 10_000; cmpthese(-5, { 'Map100k' => sub { my $sum = 0; map { $sum += $_ } 1 .. 100_000; }, 'For100k' => sub { my $sum = 0; $sum += $_ for 1 .. 100_000; }, 'Map10k' => sub { my $sum = 0; map { $sum += $_ } 1 .. 10_000; }, 'For10k' => sub { my $sum = 0; $sum += $_ for 1 .. 10_000; }, 'Map1k' => sub { my $sum = 0; map { $sum += $_ } 1 .. 1_000; }, 'For1k' => sub { my $sum = 0; $sum += $_ for 1 .. 1_000; }, }); __END__ Rate Map100k For100k Map10k For10k Map1k For1k Map100k 18.6/s -- -19% -90% -92% -99% -99% For100k 23.1/s 24% -- -88% -90% -99% -99% Map10k 187/s 905% 709% -- -22% -91% -92% For10k 239/s 1182% 933% 28% -- -89% -90% Map1k 2077/s 11052% 8880% 1009% 770% -- -13% For1k 2387/s 12715% 10219% 1175% 899% 15% --

        ___________
        Eric Hodges

        I thought map() was optimized for use in void context. I'd never use map() in such a case in this as for() is much more elegant (all IMO of course). Can you explain why the map() version takes up so much memory?

        I am gonna go out on a limb and wildly speculate that if one of them uses 96MB and the tests are accurate, it was *map*, based on the whole 'side-effect' thingy. If true, this re-emphasizes the original point that this is the one (non-preference-based) difference.

Re: When would you choose foreach instead of map?
by Anonymous Monk on May 20, 2004 at 23:18 UTC

    i use map alot. particulary when morphing data where i expect every input item to produce an output item. alot of code starts like this.

    @out = <>; print Dump(@out);

    then successive map like stages...

    #!/usr/bin/perl -l use YAML; # whitespace separated column number of hostnames (base 0) $col = shift @ARGV || 0; # hostname other junk -- 0 # other junk hostname junk -- 2 # ... @out = map { [ join('.', @{$_->[0]}), $_->[1], $_->[2] ] } map { [ [ reverse split /\./, $_->[0] ], $_->[1], $_->[2] ] } sort { $a->[0] cmp $b->[0] } map { [ join( '.', reverse @{$_->[0]} ), [ @{$_->[0]} ], $_->[1] ] + } map { [ [ split /\./, $_->[0] ], $_->[1] ] } map { chomp; @f = split; $f = splice @f, $col, 1; [ $f , [ @f ] ] +} <>; # for regular list print "-" x 50; for $x ( @out ) { print "$x->[0] -- @{$x->[2]}"; } # for permuted list print "-" x 50; for $x ( @out ) { for $y ( @{$x->[1]} ) { push @{ $d{$y} }, [ $x->[0], $x->[2] ]; } } for $k ( sort keys %d ) { print "$k:"; print "\t$_->[0] -- @{$_->[1]}" for @{ $d{$k} }; } __END__ order and/or permute hostname with info cat <<_END_ | perl foo.pl $column isc.org tra la la la la juniper.net tra la da la da paradise.gen.nz bra la da la da troublemaking.geek.nz bra la la la la hookup.net woot toot boot automagic.org bling blang blum _END_ -------------------------------------------------- hookup.net -- woot toot boot juniper.net -- tra la da la da troublemaking.geek.nz -- bra la la la la paradise.gen.nz -- bra la da la da automagic.org -- bling blang blum isc.org -- tra la la la la -------------------------------------------------- automagic: automagic.org -- bling blang blum geek: troublemaking.geek.nz -- bra la la la la gen: paradise.gen.nz -- bra la da la da hookup: hookup.net -- woot toot boot isc: isc.org -- tra la la la la juniper: juniper.net -- tra la da la da net: hookup.net -- woot toot boot juniper.net -- tra la da la da nz: troublemaking.geek.nz -- bra la la la la paradise.gen.nz -- bra la da la da org: automagic.org -- bling blang blum isc.org -- tra la la la la paradise: paradise.gen.nz -- bra la da la da troublemaking: troublemaking.geek.nz -- bra la la la la

    i find that when i try to do things like this without map that it gets uglier quicker.

    i just dig map. i'm looking forward to Perl6 because i think i can use ==> to reverse the maps

    @out = <> ==> map { chomp; [ split ] } ==> sort { $somthing } ==> map { sprintf "blah: @$_" } ; print Dump(@out);
Re: When would you choose foreach instead of map?
by ten_bytes (Initiate) on May 20, 2004 at 21:54 UTC
    I would generally try to make my code as short and full of idioms as possible...that's why I would use map
Re: When would you choose foreach instead of map?
by techra (Pilgrim) on May 21, 2004 at 17:29 UTC

    I just simply don't like map. It has its uses at its times, but for the most part I like my code to be modifiable and debuggable at every instant or iteration. Map compresses the process down way too much for my liking.

    There are those perl programmers who try to do as much with as little as possible and then are those of us who like the code to be as readable and mutable as possible.

      There are those perl programmers who try to do as much with as little as possible and then are those of us who like the code to be as readable and mutable as possible.

      I repeat an earlier statement of mine, that not understanding that part of the language does not make it less useful, effective, or in this case, readable. I use map when it is the right choice, and my code is both readable and debuggable.

      --rjray

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://355053]
Approved by adrianh
Front-paged by Enlil
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others meditating upon the Monastery: (2)
As of 2021-07-24 12:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?