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

I was reviewing some old code and found this:
foreach $key (keys %form_data) { my $temp1=$key; my $temp2=$form_data{$key}; $temp1=~s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; $temp2=~s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; $form_data.=$temp1.'='.$temp2.'&'; } $form_data=~s/\&$//;
I thought it might be better written as a map, so I came up with this:
$form_data = join('&', map { s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1) +)/seg; $form_data{$_} =~ s/([^A-Za-z0-9])/sprintf +("%%%02X", ord($1))/seg; "$_=$form_data{$_}"; } sort keys %form_data);

The map function works as designed and with no complaints, but I was surprised that I could get away with using multiple semi-colon-terminated expressions in it. Is this surprising to anyone else, or just me?

Replies are listed 'Best First'.
Re: map { ; ; ; } @array
by jeffa (Bishop) on Oct 21, 2005 at 18:36 UTC

    Why would this be any more surprising to you than using multiple expressions (not statements as you original had used in your node) in any other block, such as an if block or a while block? :) You just have to be careful that the last expression returns what you want.

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      Considering the discussion earlier this week, the "last expression" quiz, I'd say this can get pretty interesting. If the concept of "last expression" is not well-defined, then the idiomatic usage of map can be on shaky ground as well.

      Mind you, I doubt people will write the following, but it does sound poetic to include the undefined behavior:

      my @foo = map { 1 for 1 } @bar;

      --
      [ e d @ h a l l e y . c c ]

        for is not an expression, so it shouldn't be suprising that it doesn't have a well-defined value. I think the document is a little vague, but people seem to be hung up on the sentence...
        "The return value of the subroutine is the value of the last expression evaluated."
        ...which probably should be modified to something like...
        "The return value of the subroutine is the value of the last syntatic expression."
      Why do you say "not statements"? A block can certainly contain multiple statements; the last statement should be an expression, though.

      Caution: Contents may have been coded under pressure.
Re: map { ; ; ; } @array
by ikegami (Patriarch) on Oct 21, 2005 at 19:10 UTC

    You have a bug:

    map { s/.../.../seg; <-- Escapes the index. $form_data{$_} =~ ... <-- Should be using unescaped index. ... }

    Not to mention that modifying $_ without localizing it is dangerous.

    Fix:

    $form_data = join '&', map { my $key = $_; my $val = $form_data{$_}; $key =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1)) +/seg; $val =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1)) +/seg; "$key=$val" } sort keys %form_data;

    You should be using URI::Escape anyway:

    use URI::Escape qw( uri_escape ); $form_data = join '&', map { my $key = uri_escape($_); my $val = uri_escape($form_data{$_}); "$key=$val" } sort keys %form_data;

    Or even better yet, use URI::QueryParam:

    use URI; use URI::QueryParam; my $uri = URI->new(...); $uri->query_param($_ => $form_data{$_}) foreach sort keys %form_data;

    You have a second bug: The specs for this encoding (application/x-www-form-urlencoded) specifies that fields must be in the same order as the one in which they appeared in the HTML form. You're sorting them alphabetically instead.

      Thanks again! I'm not putting this code into production, but I appreciate the comments. I generally do use URI::Escape, I just wanted to preserve that code from the original block.
Re: map { ; ; ; } @array
by ikegami (Patriarch) on Oct 21, 2005 at 18:53 UTC

    map clearly says the second argument is a BLOCK, and BLOCK is defined "a sequence of statements that defines a scope [...] delimited by curly brackets" according to perlsyn.

    map, grep, eval, and do can all take BLOCKs as arguments, just like compound statements like if, while and for.

Re: map { ; ; ; } @array
by rvosa (Curate) on Oct 21, 2005 at 19:35 UTC
    I didn't know that either, so yes, this is surprising to me too. Indeed, this makes map rather more powerful.

    Good to know.
Re: map { ; ; ; } @array
by kwaping (Priest) on Oct 21, 2005 at 19:05 UTC
    Thanks for the insight. This reveals a LOT more power behind map than I had originally been aware of!