Подтвердить что ты не робот

Подпроцессы Perl звонят по ссылке или звонят по значению?

Я пытаюсь выяснить подпрограммы Perl и как они работают. Из perlsub Я понимаю, что подпрограммы являются "позывным" и что назначение (например, my(@copy) = @_;) необходимо, чтобы превратить их в вызов -по-значение.

В дальнейшем я вижу, что change вызывается по ссылке, потому что "a" и "b" меняются на "x" и "y". Но я смущен тем, почему массив не расширен с дополнительным элементом "z"?

use strict;
use Data::Dumper;

my @a = ( "a" ,"b" );

change(@a);

print Dumper(\@a);

sub change
{
    @_[0] = "x";
    @_[1] = "y";
    @_[2] = "z";
}

Вывод:

$VAR1 = [
          'x',
          'y'
        ];

Далее я передаю хеш вместо массива. Почему ключ не изменился с "a" на "x"?

use strict;
use Data::Dumper;

my %a = ( "a" => "b" );

change(%a);

print Dumper(\%a);

sub change
{
    @_[0] = "x";
    @_[1] = "y";
}

Вывод:

$VAR1 = {
    'a' => 'y'
};

Я знаю, что реальное решение состоит в передаче массива или хэша по ссылке с помощью \@, но я хотел бы точно понять поведение этих программ.

4b9b3361

Ответ 1

Perl всегда проходит по ссылке. Это просто, что иногда вызывающий проходит временные скаляры.

Первое, что вам нужно понять, это то, что аргументы subs могут быть одним и только одним: списком скаляров. * Нельзя передавать им массивы или хеши. Массивы и хеши оцениваются, возвращая список их содержимого. Это означает, что

f(@a)

совпадает с

f($a[0], $a[1], $a[2])

Perl проходит по ссылке. В частности, Perl псевдонизирует каждый из аргументов элементам @_. Изменение элементов @_ изменит скаляры, возвращаемые $a[0] и т.д., И таким образом изменит элементы @a.

Второе значение имеет то, что ключ элемента массива или хеша определяет, где элемент хранится в структуре. В противном случае $a[4] и $h{k} потребовали бы, чтобы каждый элемент массива или хеш нашел нужное значение. Это означает, что ключи не изменяются. Для перемещения значения требуется создать новый элемент с новым ключом и удалить элемент из старого ключа.

Таким образом, всякий раз, когда вы получаете ключи массива или хэша, вы получаете копию ключей. Свежие скаляры, так сказать.

Вернуться к вопросу,

f(%h)

совпадает с

f(
   my $k1 = "a", $h{a},
   my $k2 = "b", $h{b}, 
   my $k2 = "c", $h{c}, 
)

@_ по-прежнему применяется к значениям, возвращаемым %h, но некоторые из них являются лишь временными скалярами, используемыми для хранения ключа. Изменение этих параметров не будет иметь долгосрочного эффекта.

* — Некоторые встроенные модули (например, grep) больше похожи на операторы управления потоками (например, while). Они имеют свои собственные правила синтаксического анализа и, таким образом, не ограничиваются обычной моделью sub.

** — Прототипы могут влиять на то, как оценивается список аргументов, но он все равно приведет к списку скаляров.

Ответ 2

Подпрограммы Perl принимают параметры как плоские списки скаляров. Массив, переданный как параметр, для всех практических целей также является плоским списком. Даже хеш рассматривается как плоский список из одного ключа, за которым следует одно значение, за которым следует один ключ и т.д.

Плоский список не передается как ссылка, если вы не сделаете это явно. Тот факт, что модификация $_[0] изменяет $a[0], состоит в том, что элементы @_ становятся псевдонимами для элементов, переданных как параметры. Изменение $_[0] совпадает с изменением $a[0] в вашем примере. Но в то время как это приблизительно похоже на общее понятие "пройти по ссылке", поскольку оно применимо к любому языку программирования, это не означает передачу ссылки Perl; Ссылки на Perl разные (и действительно "ссылка" является перегруженным термином). Псевдоним (в Perl) является синонимом чего-то, где в качестве ссылки похож на указатель на что-то.

Как указано perlsyn, если вы назначаете @_ в целом, вы нарушаете его статус псевдонима. Также обратите внимание, что если вы попытаетесь изменить $_[0], а $_[0] будет литералом вместо переменной, вы получите сообщение об ошибке. С другой стороны, изменение $_[0] изменяет значение вызывающего абонента, если оно модифицируется. Итак, в примере один, изменение $_[0] и $_[1] распространяется обратно на @a, потому что каждый элемент @_ является псевдонимом для каждого элемента в @a.

Ваш второй пример немного сложный. Ключи хэша неизменны. Perl не предоставляет способ изменить хэш-ключ, кроме его удаления. Это означает, что $_[0] не поддается изменению. При попытке изменить $_[0] Perl не может выполнить этот запрос. Вероятно, это должно быть предупреждение, но нет. Вы видите, что плоский список, переданный ему, состоит из немодифицируемого ключа, за которым следует изменяемая стоимость и т.д. Это в основном не проблема. Я не могу придумать никаких причин модифицировать отдельные элементы хеша в том, как вы демонстрируете; поскольку хеши не имеют особого порядка, у вас не было бы простого контроля над тем, какие элементы в @_ распространяются обратно на какие значения в %a.

Как вы указали, правильным протоколом является передача \@a или \%a, так что их можно называть $_[0]->{element} или $_[0]->[0]. Несмотря на то, что обозначение немного сложнее, через какое-то время оно становится второй натурой и гораздо яснее (по моему мнению) относительно того, что происходит.

Обязательно посмотрите документацию perlsub. В частности:

Любые переданные аргументы отображаются в массиве @_. Поэтому, если вы вызываете функцию с двумя аргументами, они будут храниться в $_[0] и $_[1]. Массив @_ является локальным массивом, но его элементы являются псевдонимами для реальных скалярных параметров. В частности, если обновляется элемент $_[0], соответствующий аргумент обновляется (или возникает ошибка, если он не обновляется). Если аргумент представляет собой массив или хэш-элемент, который не существовал при вызове функции, этот элемент создается только тогда, когда (и если) он модифицирован или ссылка на него выполняется. (Некоторые более ранние версии Perl создали элемент, независимо от того, назначен ли ему элемент.) Присвоение всему массиву @_ удаляет этот псевдоним и не обновляет никаких аргументов.

Ответ 3

(Обратите внимание, что use warnings является еще более важным, чем use strict.)

@_ сам по себе не является ссылкой на что-либо, это массив (на самом деле, просто представление о стеке, хотя если вы сделаете что-то вроде ссылки на него, оно превращается в реальный массив), элементы которого каждый из них является псевдонимом переданного параметра. И те переданные параметры - это отдельные скаляры; нет понятия передачи массива или хэша (хотя вы можете передать ссылку на один).

Таким образом, сдвиги, сращения, добавленные дополнительные элементы и т.д. на @_ не влияют на что-либо переданное, хотя они могут изменить индекс или удалить из массива один из исходных псевдонимов.

Итак, когда вы вызываете change(@a), это ставит в стек две псевдонимы, от одной до $a[0] и от одной до $a[1]. change(%a) сложнее; %a выравнивается в чередующийся список ключей и значений, где значения являются фактическими значениями хэша и их изменение изменяет то, что хранится в хеше, но где ключи являются просто копиями, больше не связанными с хешем.

Ответ 4

Perl не передает массив или хеш сам по ссылке, он разворачивает записи (элементы массива или хеш-ключи и значения) в список и передает этот список функции. @_ затем позволяет получить доступ к скалярам в качестве ссылок.

Это примерно то же самое, что и запись:

@a = (1, 2, 3);

$b = \$a[2];

${$b} = 4;

@a now [1, 2, 4];

Вы заметите, что в первом случае вы не смогли добавить дополнительный элемент в @a, все, что произошло, это то, что вы изменили члены @a, которые уже существовали. Во втором случае хеш-ключи на самом деле не существуют в хэше, как скаляры, поэтому их нужно создавать как копии во временных скалярах, когда расширенный список хэша создается для передачи в функцию. Изменение этого временного скаляра не будет изменять хеш-ключ, так как это не хеш-ключ.

Если вы хотите изменить массив или хэш в функции, вам нужно будет передать ссылку на контейнер:

change(\%foo);

sub change {
   $_[0]->{a} = 1;
}

Ответ 5

Во-первых, вы смешиваете @sigil как указание массива. Это список. Когда вы вызываете Change (@a), вы передаете список функции, а не объект массива.

Случай с хэшем несколько отличается. Perl оценивает ваш вызов в списке и передает значения в виде списка.