Порядок выполнения с оператором запятой в Perl


Простите плохую читабельность моих примеров, но этот код предназначен для игры в гольф, а не для производственного кода.

Рассмотрим следующий сценарий:

print'+'x$z,($z=1,$w)?'':$_ for 1..3;

Это печатает, как я и ожидал, 1+2+3. Переменная $z изначально не назначена, поэтому '+'x$z вычисляется как пустая; после этого $z устанавливается в 1, поэтому '+'x$z Теперь вычисляется как +.

Однако, если я изменю это так, что $z содержит само +:

print$z,($z='+',$w)?'':$_ for 1..3;

Теперь скрипт выводит +1+2+3. Это, кажется, предложите мне, чтобы порядок исполнения был другим, но я не понимаю, почему.

Каковы точные правила относительно порядка выполнения, которые заставляют эти два примера вести себя по-разному? Является ли порядок исполнения даже четко определенным?
3 4

3 ответа:

Аргументы передаются по ссылке в Perl.

print $z, ($z='+',$w) ? '' : $_;

В основном

{
   local @_;
   alias $_[0] = $z;
   alias $_[1] = ($z='+',$w) ? '' : $_;
   &print;
}

Поскольку $_[0] имеет псевдоним $z, изменения в $z отражаются в $_[0], даже если эти изменения происходят после вычисления аргумента.

Вы можете увидеть тот же эффект в следующем:

my $x = 3;
sub f { 
   ++$x;
   print("$_[0]\n");
}
f($x);  # 4

Вот моя попытка разобраться в двух ваших примерах. Рассмотрим такой сценарий:

use strict;
use warnings;
use Data::Dumper;

sub dd { print Dumper(\@_) }

my $z = 0;

dd($z + 2, ($z = 1));  # Similar to your Version 1.
dd($z,     ($z = 1));  # Similar to your Version 2.

Вывод, с некоторыми комментариями:

$VAR1 = [
          2,              # The constant 2.
          1               # $z by reference, which prints as 1.
        ];
$VAR1 = [
          1,              # $z by reference.
          ${\$VAR1->[0]}  # Ditto.
        ];

В версии 1 Perl не может передать $z + 2 непосредственно в dd(). Он должен оценить выражение. Результат этой оценки (Константа 2) передается в качестве первого аргумента. Второй аргумент также вычисляется: $z устанавливается в 1, возвращаемое значение присваивания равно $z, а затем $z передается по ссылке на dd().

В Версия 2, Perl может просто передать первый аргумент непосредственно по ссылке: нет необходимости вычислять большее выражение. Второй аргумент такой же, как и в версии 1. В результате dd() получает одну и ту же переменную дважды, как показано в выводе Data::Dumper.

Оригинальный Ответ

Вам нужно прогнать это через perl -MO=Deparse,-p. Первый БИТ кода показывает следующее:

print(('+' x $z), ((($z = 1), $w) ? '' : $_)) foreach (1 .. 3);

Но второй бит кода показывает следующее:

print($z, ((($z = '+'), $w) ? '' : $_)) foreach (1 .. 3);

Сбитый с толку и убитый горем

по-видимому, этого оказалось недостаточно, чтобы достаточно объяснить некоторые вещи некоторым людям. Этого не должно было случиться, потому что я считал это совершенно ясным.

Принятое решение ошибочно утверждает, что это каким-то образом связано с факт, что Perl передает скалярные переменные по неявной ссылке. Это не имеет к этому никакого отношения. Это простой вопрос приоритета и порядка оценки. Я предполагал, что выход Deparse должен был прояснить это.

Очевидно, некоторые до сих пор пребывают в замешательстве.

Первая Версия

Очень хорошо, вот ваше объяснение, все выкрашенное на серебряном блюде для вас.

Это:

print'+'x$z,($z=1,$w)?'':$_ for 1..3;

Эквивалентно, любезно Deparse и некоторые дополнительные формирование, к этому:

{
    ($w, $z) = (undef, undef);
    for (1..3) {
        print(("+" x $z), ((($z = 1), $w) ? "" : $_))
    }
} continue {
    print "\n";
}

Теперь, разворачивая петлю и отделяя то, что происходит, когда производит это:

{
     ($w, $z) = (undef, undef);
    {
        local $_ = 1;
        $temp = "+" x $z; # $z is undef
        $z = 1;
        print $temp, $_;
    }
    {
        local $_ = 2;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    }
    {
        local $_ = 3;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    }
} continue {
    print "\n";
}

Все три из них дают одинаковый результат: 1+2+3.

Вторая Версия

Теперь мы снова начнем с оригинала:
print$z,($z='+',$w)?'':$_ for 1..3;

И выдать исходящую версию:

{
    ($w, $z) = (undef, undef);
    for (1..3) {
        print($z, ((($z = "+"), $w) ? "" : $_));
    }
} continue {
    print "\n";
}

С последующим циклом разворачивания версии:

{
    ($w, $z) = (undef, undef);
    {
        local $_ = 1;
        $z = "+";
        print $z, $_;
    }
    {
        local $_ = 2;
        $z = "+";
        print $z, $_;
    }
    {
        local $_ = 3;
        $z = "+";
        print $z, $_;
    }
} continue {
    print "\n";
}

Все три версии, по причинам, которые я действительно надеюсь, теперь совершенно ясны печатают одно и то же результат: +1+2+3.


Для Дальнейшего Просветления

Лучший способ отследить то, что происходит, - это наложить на него след:

tie $z, "Tie::Trace", "z";
tie $w, "Tie::Trace", "w";

($w, $z) = (undef, undef);
print'+'x$z,($z=1,$w)?'':$_ for 1..3;
print "\n";

{
    ($w, $z) = (undef, undef);
    for (1..3) {
        print(("+" x $z), ((($z = 1), $w) ? "" : $_))
    }
} continue {
    print "\n";
}

{
     ($w, $z) = (undef, undef);
    {
        local $_ = 1;
        $temp = "+" x $z; # $z is undef
        $z = 1;
        print $temp, $_;
    }
    {
        local $_ = 2;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    }
    {
        local $_ = 3;
        $temp = "+" x $z; # $z is 1
        $z = 1;
        $_ = $_;
        print $temp, $_;
    }
} continue {
    print "\n";
}

($w, $z) = (undef, undef);
print$z,($z='+',$w)?'':$_ for 1..3;
print "\n";

{
    ($w, $z) = (undef, undef);
    for (1..3) {
        print($z, ((($z = "+"), $w) ? "" : $_));
    }
} continue {
    print "\n";
}

{
    ($w, $z) = (undef, undef);
    {
        local $_ = 1;
        $z = "+";
        print $z, $_;
    }
    {
        local $_ = 2;
        $z = "+";
        print $z, $_;
    }
    {
        local $_ = 3;
        $z = "+";
        print $z, $_;
    }
} continue {
    print "\n";
}

package Tie::Trace;

sub TIESCALAR {
    my($class, $name, $value) = @_;
    return bless {
        NAME  => $name,
        VALUE => undef,
    } => $class;
}

sub FETCH {
    my($self) = @_;
    my $name = '$' . $self->{NAME};
    my $value = $self->{VALUE};
    print STDERR "[reading value ", defined($value) ? $value : "undef",
            " from $name]\n";
    return $value;
}

sub STORE {
    my($self, $value) = @_;
    my $name = '$' . $self->{NAME};
    print STDERR "[writing value ", defined($value) ? $value : "undef",
            " into $name]\n";
    $self->{VALUE} = $value;
    return $value;
}

Когда вы запускаете его, он производит этот довольно приятный результат:

[writing value undef into $w]
[writing value undef into $z]
[reading value undef from $z]
[reading value undef from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
1+2+3
[writing value undef into $w]
[writing value undef into $z]
[reading value undef from $z]
[reading value undef from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value undef from $w]
1+2+3
[writing value undef into $w]
[writing value undef into $z]
[reading value undef from $z]
[reading value undef from $z]
[writing value 1 into $z]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
[reading value 1 from $z]
[reading value 1 from $z]
[writing value 1 into $z]
1+2+3
[writing value undef into $w]
[writing value undef into $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
+1+2+3
[writing value undef into $w]
[writing value undef into $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
[writing value + into $z]
[reading value undef from $w]
[reading value + from $z]
+1+2+3
[writing value undef into $w]
[writing value undef into $z]
[writing value + into $z]
[reading value + from $z]
[writing value + into $z]
[reading value + from $z]
[writing value + into $z]
[reading value + from $z]
+1+2+3

Резюме

Теперь я старательно продемонстрировал, что то, что здесь происходит на самом деле, не имеет никакого отношения к проходящей ссылке. Это имеет отношение только к порядку оценки и ни к чему другому.