in reply to Map Function Weirdness

"WTF!?"

I love what Mark Jason Dominus said on the subject of questions where something didn't work: "But it does work. It just doesn't do what you wanted. There is a difference, you know." So let's examine why it didn't work as you expected in your situation.

The results you are getting are entirely predictable, and entirely consistent with how Perl evaluates expressions dealing with truth/falsehood. The behavior you're seeing would hold true for subroutines, do{} blocks, and anywhere else where a chain of events could force you to read a few lines higher in the code to find the final expression that got evaluated. This is actually one reason why Damian Conway, in Perl Best Practices suggests that all subroutines should invoke return; to reduce the potential for astonishment when the final expression to get evaluated happens to not be what the programmer originally considered.

Here's what's happening with your snippet:

Let's take the easiest question first: Why does the behavior "do what you want" when you uncomment the final line of the map statement: "#    $_;". Why? Because map returns the value of the last statement it evaluates. If the last statement is $_;, on each iteration it returns what's in $_.

Now let's take the next easiest situation: The situation where $_ is greater than $max. In those cases, the last statement that gets evaluated (assuming you comment out the "$_;" line) is this: "$max=$_", which evaluates to whatever value was in $_, which happens to be what your program currently thinks is the maximum value. On the first iteration that's 40. Later on, that's 50.

Now we'll look at the hardest concept: What happens when $_ is less than $max? In that case, your conditional is false, and the block, "{$max=$_}" doesn't get evaluated. So what was the last thing to get evaluated? "$_>$max", and it evaluated to false.

The final thing to get evaluated on iteration two, three, and four was a conditional statement that evaluated to false. That's what got returned via map on that iteration.

It may have been made a little more confusing by virtue of the fact that hashes have no implicit order. You take what you get, essentially. It turns out that on the first iteration, the value that is being iterated over is '40'. On the 2nd, 3rd, and 4th, it's some value less than 40 (which is why your if() conditional evaluates to false). On the final 5th iteration, it the iterator happens to fall on the hash value of 50. The conditional evaluates to true once again, $_ sets $max, and that value gets returned rather than the boolean value of the conditional within the if() statement.

This is covered in the manual:

If you want some painstakingly boring proof that Perl was just doing what you asked it to do, and that your associating the issue with map weirdness was overlooking all of the other similar situations where it may not seem so weird yet is 100% consistent, read on:

use strict; use warnings; use Data::Dumper; my %hash=(1=>10,2=>20,3=>30,4=>40,5=>50); my $max=0; my @out=map{ $_=$hash{$_}; if($_>$max){$max=$_} # $_; }keys %hash; sub mytest { if ( 0 > 1 ) { 1; } } print "defined(mytest()) is ", defined( mytest() ) ? "defined.\n" : "undefined.\n"; print "mytest() is ", mytest() ? "true.\n" : "false.\n"; print "mytest() returns: '", mytest(), "'\n\n"; print "defined(\$out[2]) is ", defined( $out[2] ) ? "defined.\n" : "undefined\n"; print "\$out[2] is ", $out[2] ? "true.\n" : "false.\n"; print "\$out[2] returns: '", $out[2], "'\n\n"; print "defined(if(0>1){1}) is ", defined( do{ if(0>1){1} } ) ? "defined.\n" : "undefined.\n"; print "if(0>1){1} is ", do{ if(0>1){1} } ? "true.\n" : "false.\n"; print "if(0>1){1} returns: '", do{ if(0>1){1} }, "'\n\n"; print "defined(do{}) returns: ", defined( do{} ) ? "a defined value.\n\n" : "an undefined value.\n\n"; print "defined((map{if(0>1){1}}(1))[0]) is ", defined((map{if(0>1){1}}(1))[0]) ? "defined.\n" : "undefined.\n"; print "(map{if(0>1){1}}(1))[0] is ", (map{if(0>1){1}}(1))[0] ? "true.\n" : "false.\n"; print "(map{if(0>1){1}}(1))[0] returns: '", (map{if(0>1){1}}(1))[0], "'\n\n"; print "defined((map{\$_=\$hash{\$_};if(\$_>$max){\$max=\$_}}keys %hash +)[2]) is ", defined((map{$_=$hash{$_};if($_>$max){$max=$_}}keys %hash)[2]) ? "defined.\n" : "undefined.\n"; print "(map{\$_=\$hash{\$_};if(\$_>\$max){\$max=\$_}}keys %hash)[2] is + ", ( do{map{$_=$hash{$_};if($_>$max){$max=$_}} keys %hash} )[2] ? "true.\n" : "false.\n"; print "(map{\$_=\$hash{\$_};if(\$_>\$max){\$max=\$_}}keys %hash)[2] re +turns: '", (map{$_=$hash{$_};if($_>$max){$max=$_}} keys %hash)[2], "'\n\n";

And the painfully boring, predictable output:

defined(mytest()) is defined. mytest() is false. mytest() returns: '' defined($out[2]) is defined. $out[2] is false. $out[2] returns: '' defined(if(0>1){1}) is defined. if(0>1){1} is false. if(0>1){1} returns: '' defined(do{}) returns: an undefined value. defined((map{if(0>1){1}}(1))[0]) is defined. (map{if(0>1){1}}(1))[0] is false. (map{if(0>1){1}}(1))[0] returns: '' defined((map{$_=$hash{$_};if($_>50){$max=$_}}keys %hash)[2]) is define +d. (map{$_=$hash{$_};if($_>$max){$max=$_}}keys %hash)[2] is false. (map{$_=$hash{$_};if($_>$max){$max=$_}}keys %hash)[2] returns: ''

Dave