Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does wantarray return in scalar context when calling foo() || die?

Tags:

list

perl

I've just spent a bunch of time debugging an issue that I've traced back to wantarray(). I've distilled it down to this test case. (Ignore the fact that $! won't have any useful info in this scenario). What I'd like to know is why wantarray does not think it's being called in LIST context in the second example:

#!/usr/bin/env perl

use strict;
use warnings;
use Test::More;

{
    my ( $one, $two ) = foo();
    is( $one, 'a', 'just foo' );
    is( $two, 'b', 'just foo' );
}

{
    my ( $one, $two ) = foo() || die $!;
    is( $one, 'a', '|| die' );
    is( $two, 'b', '|| die' );
}


done_testing();

sub foo {
    return wantarray ? ( 'a', 'b' ) : 'bar';
}

The output of this test is:

$ prove -v wantarray.pl
wantarray.pl ..
ok 1 - just foo
ok 2 - just foo
not ok 3 - || die
not ok 4 - || die
1..4

#   Failed test '|| die'
#   at wantarray.pl line 15.
#          got: 'bar'
#     expected: 'a'

#   Failed test '|| die'
#   at wantarray.pl line 16.
#          got: undef
#     expected: 'b'
# Looks like you failed 2 tests of 4.
Dubious, test returned 2 (wstat 512, 0x200)
Failed 2/4 subtests

Test Summary Report
-------------------
wantarray.pl (Wstat: 512 Tests: 4 Failed: 2)
  Failed tests:  3-4
    Non-zero exit status: 2
    Files=1, Tests=4,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.02 cusr  0.00 csys =  0.06 CPU)
    Result: FAIL
like image 253
oalders Avatar asked Jul 08 '13 21:07

oalders


3 Answers

Because it's not being called in list context. || imposes scalar context on its left hand side, and its left hand side in this case is the expression foo().

You should write instead

my ( $one, $two ) = foo() or die $!;

The or operator binds even more loosely than the assignment operator, so now its LHS is the entire expression my ($one, $two) = foo(), and foo's context is determined by the list assignment operator, and everyone is happy.

like image 63
hobbs Avatar answered Oct 16 '22 05:10

hobbs


The reason is due to the precedence of the || operator. Your statement is basically parsed like this:

my ( $one, $two ) = ( foo() || die $! );

The || puts its operands in scalar context in this case.

On the other hand, if you change || to or, which has much lower precedence, your tests will pass.

like image 25
friedo Avatar answered Oct 16 '22 07:10

friedo


Logical or (||) is a scalar operator, so using it will force the evaluation of foo() to become scalar context.

Try this:

my @a = 10 .. 100;
print(@a || 2), "\n";
# prints 91

You'd expect this to print the elements in @a if it wasn't because the array has been evaluated in scalar context.

like image 4
mzedeler Avatar answered Oct 16 '22 05:10

mzedeler