Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Confessions of a back-alley map abuser

by dimar (Curate)
on Dec 18, 2004 at 18:33 UTC ( #415882=perlmeditation: print w/replies, xml ) Need Help??

The map abuser likes to pick up the pipe

Sometimes it is enjoyable to abuse map. For example, it is possible to assign one scalar to another while using map statements as an intermediate pass through 'filter'.

This is a convienient typographical convention because you can 'chain' results together in a manner similar to 'pipes' in the Unix OS shell. This is useful because each step in the chain acts *in parallel* as an incremental and mutually substitutable filter. It is also useful because it allows you to both assign and modify a scalar in one statement.

The ugly side of map abuse

By its design, map is intended for use with arrays, not scalars. This design characteristic results in some fairly ugly artefacts in our code when we try to force map to do what we want.

  • ugly0: parenthesis around the scalar used to invoke list context
  • ugly1: special $_ variable tacked on just to get what we want out of map
  • ugly2: scalar enclosed in a CODEREF to prevent it from being modified by map
my $sBegin = "hello world"; my $sEnd = ""; ($sEnd) = ### ugly0 map{uc($_);} map{s/world/todo el mundo/;$_;} ### ugly1 map{s/hello/goodbye/g;$_;} ### ugly1 (sub{$sBegin}->()); ### ugly0, ugly2 print $sEnd;

Replies are listed 'Best First'.
Re: Confessions of a back-alley map abuser
by Juerd (Abbot) on Dec 18, 2004 at 23:09 UTC

    By its design, map is intended for use with arrays, not scalars.

    lists, not arrays!

    Arrays are those things that have @. And even then, an array used in list context evaluates to a list of its elements.

    @foo # array @Foo::bar # array @$foo # array @{ $bar } # array @{ $blah{blah}[1] } # array @list # array with a confusing name $array # scalar (possibly a reference) [ ... ] # scalar (reference to anonymous array) \@array # scalar (reference to named array) *foo{ARRAY} # scalar (reference to the package global @foo) print @array # list (print "gets" a list, not an array, # because @array is in list context) return @array # list! (again, array in list context) \(1, 2, 3) # list of references, not a reference to a list @foo = # array map { $_ + 1 } # list map { s/\n//g } # list @bar # list! (array in list context)
    Subs get and return lists. List operators (like map) take lists. Arrays can be referenced, lists cannot. "Array context" exists, but only syntax-wise (\@ prototype or special syntax). Most of the time "list context" is what you really mean. Oh, and arrays can have names, lists cannot.

    Please, learn the difference. It's confusing enough already even if you use the *correct* words.

    Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

    A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Confessions of a back-alley map abuser
by Zaxo (Archbishop) on Dec 18, 2004 at 19:39 UTC

    You've put your chaining at a disadvantage in the ugliness department by illustrating it with mutators of $_. This technique shines in combination with other list operators like grep and sort, and when wrapped in a subroutine so that it acts on a copy of @_.

    sub quux { my @args = @_; map { foo $_ } map { bar $_ } map { baz $_ } @args; }

    The real alternative is nested sub calls. While those look messy in C, with all the parens to be balanced, they have an equally nice expression in Perl, my $quux = foo bar baz $_;

    One of the better reasons to use map in this way is the aliasing of localized $_ to each argument in turn. That is not a big advantage in your actions on a list of one element, but is a nice shorthand when operations act on $_ by default. You missed an opportunity to use that in the map { uc } stage.

    Of course, this is all extremely powerful when acting on lists. The Schwartzian Transform is on anybody's list of "Best Dressed Perl".

    After Compline,
    Zaxo

      The Schwartzian Transform is on anybody's list of "Best Dressed Perl".

      Heh. Not the VP of Technology of a former workplace of mine who denounced an instance of the Schwartzian Transform in my code as "unreadable and unmaintainable." (It was commented as an ST, so any who didn't know it could look it up, but, of course, he already knew it was bad so he could save himself the bother of learning something new.)

      Of course, his idea of good Perl was Perl trying to look like Java...

Re: Confessions of a back-alley map abuser
by Sidhekin (Priest) on Dec 18, 2004 at 18:48 UTC

    I disagree that ugly0 and ugly1 really are ugly. ugly0 is just list assignment syntax. ugly1 is just recognition that we need a return value from the map block. And in my mind, the only abuse of map, is when you use it in void context or throw away the return value(s).

    Having said that, your example does not so much look like a pipeline to me. It looks more like a succession of operations:

    my $sBegin = "hello world"; my $sEnd = do { local $_ = $sBegin; s/hello/goodbye/g; s/world/todo el mondo/; uc; }; print $sEnd;

    When the only tool you have is map, every problem looks like a pipeline?

    print "Just another Perl ${\(trickster and hacker)},"
    The Sidhekin proves Sidhe did it!

      I like to use "for" for that kind of thing:

      my $sBegin = "hello world"; my $sEnd = $sBegin; for ($sEnd) { s/hello/goodbye/g; s/world/todo el mondo/; uc; }; print $sEnd;

        I like to use "for" for that kind of thing

        Me too, but I have found do{} is more readable. For one thing, it saves you from your bug: Your version throws away the uppercase string and so prints just "goodbye todo el mondo". My version would look like this:

        my $sBegin = "hello world"; my $sEnd; for ($sEnd = $sBegin) { s/hello/goodbye/g; s/world/todo el mondo/; $_ = uc; }; print $sEnd;

        TIMTOWTDI, but I think do{} is the more readable here.

        Update: I trust you. No problem. But the bug still illustrates how the void context makes the for loop slightly less readable here. Such a bug (update: or the more likely opposite bug) could not as easily occur in the do{} block.

        print "Just another Perl ${\(trickster and hacker)},"
        The Sidhekin proves Sidhe did it!

Re: Confessions of a back-alley map abuser
by hv (Prior) on Dec 18, 2004 at 18:41 UTC

    ugly2 can be slightly less ugly:

    map {...} "$sBegin"; # quote to duplicate string

    Hugo

      "$sBegin" is not a true identity function. It forces string context on the variable, which may cause surprising problems in some code.

      "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

Re: Confessions of a back-alley map abuser
by Aristotle (Chancellor) on Dec 21, 2004 at 03:41 UTC

    sub{$sBegin}->() can be written @{ [ $sBegin ] }.

    The one case were I frequently find myself “abusing” map in this fashion is to qr// a string built with function calls, usually a join with a bar:

    my( $rx ) = map qr/($_)/, join '|', @alternative;

    The other way to express this in condensed form is

    my( $rx ) = do { local $" = '|'; qr/(@alternative)/ };

    I'm not sure why I prefer the former (they're both kind of kludgy), but I do. :-)

    Makeshifts last the longest.

Re: Confessions of a back-alley map abuser
by ccn (Vicar) on Dec 18, 2004 at 18:51 UTC

    The last "ugly0" is redundant

Re: Confessions of a back-alley map abuser
by Anonymous Monk on Dec 20, 2004 at 16:47 UTC
    I like to use local $_ = $_; to prevent the arguments from being modified:
    ($sEnd) = map {uc} map {s/world/todo el mundo/; $_} map {local $_ = $_; s/hello/goodbye/; $_} $sBegin; # Or: ($sEnd) = map {uc} map {s/world/todo el mundo/; $_} map {s/hello/goodbye/; $_} map {local $_ = $_} $sBegin;

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://415882]
Approved by atcroft
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others surveying the Monastery: (3)
As of 2023-09-26 14:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?