On the one hand, 'map' returns an output list that results from applying some block of code to an input list. This leads to the often-repeated mantra "don't use map in a void context". (Why? Well, let's not digress...)

On the other hand, 'map' will alter the elements of the input list if its block of code modifies $_ in any way, because within the block, $_ functions as a reference to a list element on each iteration -- and of course this is often (usually) how 'map' is used. This can lead to the sort of trouble discussed in this recent meditation, when the input list happens to contain read-only data.

One could argue that this dual result from the mapped code block -- doing in-place editing of the input list and also returning the edited list -- seems to be overdoing it, or is somewhat redundant.

There are many examples of functions (not just in Perl, but generally) that alter the contents of variables in-place and return the original (unaltered) values, or that return altered values and leave the input variables unchanged -- that is, their changes are applied either to the input or to the output, but not to both.

So, maybe this is just a "SoPW" question: is there something that works like 'map' but will not alter the input list? (Or, less intuitively, modify the input list and return the list of unaltered values?)

Apart from cases where the input list contains read-only elements, there are common situations where it would be handy to map one list into another, and have both versions of the list available for later use. To the best of my knowledge, the alternatives for doing this are:

# ------------------ # FIRST ALTERNATIVE: @out_list = @in_list; map { s/foo(.*?)bar/$1 baz/ } @out_list; # ACK!! ARRGGHH!! map in void context! how bad is that? # (Well, seriously: how bad _is_ that, compared to the # other alternatives below? But let's not digress...) # ------------------- # SECOND ALTERNATIVE: @out_list = map { s/foo(.*?)bar/$1 baz/ } split /\x0/, join("\x0",@in_ +list); # okay, map is used "correctly", and this even works when # the input list and block processing are numeric, but... # seems like too much busy-work just to protect @input_list # from being altered by map. # ------------------ # THIRD ALTERNATIVE: @out_list = map {$j=$_; $j=~s/foo(.*?)bar/$1 baz/; $j} @in_list; # at this point, might as well use "foreach (@input_list) {...}" inste +ad
Are there other (better) alternatives? If there aren't, shouldn't there be? Is it just silly of me to yearn for this sort of thing?

Replies are listed 'Best First'.
Re: A "harmless" alternative to 'map{}'?
by japhy (Canon) on Sep 09, 2002 at 04:59 UTC
    I'd use s/this/that/ for @new = @old;

    _____________________________________________________
    Jeff[japhy]Pinyan: Perl, regex, and perl hacker, who'd like a job (NYC-area)
    s++=END;++y(;-P)}y js++=;shajsj<++y(p-q)}?print:??;

      Ah, yes -- that's just the sort of thing I was looking for. (I guess my post was more SoPW than Meditation after all.) Thanks!
Re: A "harmless" alternative to 'map{}'?
by Aristotle (Chancellor) on Sep 09, 2002 at 05:58 UTC
    Third alternative:
    my $x; @out_list = map { ($x = $_) =~ s/foo(.*?)bar/$1 baz/; $x } @in_list;
    Aliasing $_ to the input element is done for efficiency reasons. Note that it doesn't have to be $_ that the callback returns; you can modify the input list and at the same time generate a different output list from it. For a silly example, my @subst = map { s/\s+/_/g } @list; will turn any whitespace sequences in the input list elements into underscores and return a list that contains the number of substitutions effected for each element. Granted this example is silly, but on occasion this "dual output" behaviour can be very useful. In other cases you can copy $_ to protect it. Of course the "other cases" are more common so this is a violation of Huffman coding, but oh well..

    Makeshifts last the longest.

Re: A "harmless" alternative to 'map{}'?
by shotgunefx (Parson) on Sep 09, 2002 at 08:52 UTC
    Personally, I almost never get bitten by this and it's pretty easy to avoid if you know about it but I came across an oddity myself.
    perl -e '@a = map {$_+=1 } (1..3); print "@a\n";' # OK perl -e '@a = map {$_+=1 } (1,2,3); print "@a\n";' # Modification of a + read-only value attempted at -e line 1.
    Internally, what's the difference? Why would the first not fail?

    -Lee

    "To be civilized is to deny one's nature."
      I think this has to do with the optimization of the .. operator when fed into a loop. It is handled differently so that things like
      for (1..1000000) {...}
      won't build a million element array before starting the loop.

      --

      flounder

Re: A "harmless" alternative to 'map{}'?
by demerphq (Chancellor) on Sep 09, 2002 at 11:33 UTC
    Hmm, I think you might have got caught up in a side street on this one. For instance for first example _shouldn't_ be written that way. When people say "dont use map in void context" they say it for good reasons.
    @out_list=@in_list; s/foo(.*?)bar/$1 baz/ for @in_list;
    Update:
    I like japhys version, and I'd probably use it myself, but I can see some people saying that the above version is more understandable. japhy++

    Your second method leaves a lot to be desired (its unusable for a variety of reasons), and your third version is workable, although i'd probably write it
    @out_list = map {local $_=$_; s/foo(.*?)bar/$1 baz/; $_ } @in_list;
    Anyway, heres a sub so you can write it the way that feels comfortable
    sub ro_map (&@){ my $sub=shift; my @return; foreach (@_) { local $_=$_; push @return,$sub->(); } @return } my @out=ro_map{s/x/y/g}@in; print "original: @in\n"; print "changed : @out\n";
    One could argue that this dual result from the mapped code block -- doing in-place editing of the input list and also returning the edited list -- seems to be overdoing it, or is somewhat redundant.

    I dont think you would get far with this argument. Howabout this:

    # lc the filenames and create a set of filespecs from them. my @filespecs=map{ $_=lc($_); File::Spec->joinpath($path,$_) } @files;
    HTH

    Now on to read the other comments. :-)

    Yves / DeMerphq
    ---
    Software Engineering is Programming when you can't. -- E. W. Dijkstra (RIP)

Re: A "harmless" alternative to 'map{}'?
by Zaxo (Archbishop) on Sep 09, 2002 at 05:18 UTC

    How about wrapping it in a sub, thusly?

    sub new_lamps_for_old (\@) { my $old; map { $old = $_; $_ = Lamp->new(); $old; } @{$_[0]}; }

    The prototype is used to guarantee that the map acts on lvalues.

    I have been known to propose placing map in void context when modifying its arguments, just to call attention to the fact. I didn't really mind losing on that proposal.

    After Compline,
    Zaxo

      The prototype is used to guarantee that the map acts on lvalues.

      No. Prototypes cannot provide any guarantee.

      - Yes, I reinvent wheels.
      - Spam: Visit eurotraQ.
      

        Prototypes cannot provide any guarantee.

        The \@ prototype guarantees barfing if the argument doesn't start with an @, though. And seeing that arrays tend to be lvalues...

        Am I missing anything?

        — Arien

Re: A "harmless" alternative to 'map{}'?
by Anonymous Monk on Sep 09, 2002 at 11:44 UTC
    Yes.
    my @changed = map {local $_ = $_; ...} @original;