Как @_ работает в подпрограммах Perl?

Я всегда был уверен, что если я передам подпрограмму Perl простой скаляр, он никогда не сможет изменить свое значение вне подпрограммы. То есть:

my $x = 100;
foo($x);
# without knowing anything about foo(), I'm sure $x still == 100

Итак, если я хочу foo() изменить x, я должен передать ему ссылку на x.

Тогда я узнал, что это не так:

sub foo {
 $_[0] = 'CHANGED!';
}
my $x = 100;
foo($x);
print $x, "\n"; # prints 'CHANGED!'

И то же самое относится к элементам массива:

my @arr = (1,2,3);
print $arr[0], "\n"; # prints '1'
foo($arr[0]);
print $arr[0], "\n"; # prints 'CHANGED!'

Это меня удивило. Как это работает? Разве только подпрограмма получает значение аргумента? Как узнать его адрес?

Ответ 1

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

поэтому в этом суб:

sub example {
   # @_ is an alias to the arguments
   my ($x, $y, @rest) = @_;  # $x $y and @rest contain copies of the values
   my $args = \@_;  # $args contains a reference to @_ which maintains aliases
}

Обратите внимание, что это псевдонижение происходит после расширения списка, поэтому, если вы передали массив в example, массив расширяется в контексте списка, а @_ задается псевдонимами каждого элемента массива (но сам массив недоступен для example). Если вы захотите последнего, вы передадите ссылку на массив.

Слияние аргументов подпрограммы - очень полезная функция, но ее необходимо использовать с осторожностью. Чтобы предотвратить непреднамеренную модификацию внешних переменных, в Perl 6 вы должны указать, что вы хотите записывать аргументы с псевдонимом с помощью is rw.

Один из менее известных, но полезных трюков - использовать эту функцию псевдонимов для создания массива refs псевдонимов

my ($x, $y) = (1, 2);

my $alias = sub {\@_}->($x, $y);

$$alias[1]++;  # $y is now 3

или псевдонимы:

my $slice = sub {\@_}->(@somearray[3 .. 10]);  

также выясняется, что использование sub {\@_}->(LIST) для создания массива из списка на самом деле быстрее, чем
[ LIST ], поскольку Perl не нужно копировать каждое значение. Конечно, недостаток (или потенциал роста в зависимости от вашей точки зрения) заключается в том, что значения остаются псевдонимами, поэтому вы не можете их изменять без изменения оригиналов.

Как отмечает tchrist в комментарии к другому ответу, когда вы используете любую конструкцию псевдонимов Perl на @_, $_, которую они предоставляют вам, также является псевдонимом исходных аргументов подпрограммы. Например:

sub trim {s!^\s+!!, s!\s+$!! for @_}  # in place trimming of white space

Наконец, все это поведение является nestable, поэтому при использовании @_ (или его части) в списке аргументов другой подпрограммы он также получает псевдонимы для первых аргументов подпрограммы:

sub add_1 {$_[0] += 1}

sub add_2 {
    add_1(@_) for 1 .. 2;
}

Ответ 2

Все это подробно описано в perldoc perlsub. Например:

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