Почему returnarray возвращается в скалярном контексте при вызове foo() || умереть?

Я только что потратил кучу времени, отлаживая проблему, которую я проследил до wantarray(). Я переусердствовал до этого теста. (Игнорируйте тот факт, что $! не будет иметь никакой полезной информации в этом сценарии). Я хотел бы знать, почему wantarray не считает, что он вызван в контексте LIST во втором примере:

#!/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';
}

Выход этого теста:

$ 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

Ответ 1

Потому что он не вызывается в контексте списка. || накладывает скалярный контекст на его левую сторону, а его левая сторона в этом случае является выражением foo().

Вместо этого вы должны написать

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

Оператор or связывается даже более свободно, чем оператор присваивания, поэтому теперь его LHS является всем выражением my ($one, $two) = foo(), а контекст foo определяется оператором присваивания списка, и все довольны.

Ответ 2

Причина связана с приоритетом оператора ||. Ваше выражение в основном анализируется следующим образом:

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

В этом случае || помещает свои операнды в скалярный контекст.

С другой стороны, если вы измените || на or, который имеет гораздо более низкий приоритет, ваши тесты пройдут.

Ответ 3

Логический или (||) - скалярный оператор, поэтому его использование заставит оценку foo() стать скалярным контекстом.

Попробуйте следующее:

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

Вы ожидаете, что это напечатает элементы в @a, если это не так, потому что массив был оценен в скалярном контексте.