Программист прочитал в документации на функцию splice
,
что с помощью нее можно реализовать push
, pop
,
shift
, unshift
и присваивание элементу массива.
Программист решил попробовать и реализовать фунцию rpush
,
которая работала бы как push
, но в качестве первого аргумента
принимала бы ссылку на массив.
После этого программист заменил в своем проекте
все push @$arrref, ...
на rpush $arrref, ...
.
И что же из этого получилось?
Можно скачать примеры использования и посмотреть, что получилось: rpush.pl
Здесь $var
не меняется:
А здесь – меняется:
Разница в поведении rpush $arrref, ...
и
push @$arrref, ...
появляется тогда, когда
переменная $arrref
содержит значение undef
перед вызовом.
Итак, что же происходит в этом случае?
В случае push @$arrref, ...
происходит
самооживление ссылки
(autovivification, см. также
perldoc perlref).
Самооживление – очень удобное свойства Perl:
если переменная со значением undef
используется в качестве Л-значения так, будто
она является ссылкой, то она и становится ссылкой.
На массив, на хеш, на скаляр –
в зависимости от контекста присваивания.
В случае push @$arrref, ...
переменная
$arrref
становится ссылкой на пустой массив,
и в него добавляются значения, перечисленные в push
.
В случае функции rpush
при вызове
splice(@$arr, @$arr, 0, @_);
тоже происходит самооживление,
однако самооживляется переменная $arr
,
существующая только во время выполнения функции.
Переменная $arrref
, переданная функции снаружи,
никак не меняется.
Поэтому после rpush $arrref, ...
в $arrref
останется undef
, а это явно не то,
чего хотел программист.
Можно сделать так, чтобы rpush
все-таки
оживляла неинициализированные переменные:
Здесь не происходит копирование переданного параметра
во временную переменную, и
splice
выполняется над $_[0]
– алиасом
для первого фактического аргумента.
Или же можно сначала оживить неинициализированную ссылку, а затем скопировать ее во временную переменную и работать как раньше:
Этот вариант кажется нам более читаемым.
Кроме того, с версии perl 5.14 встроенный push тоже умеет принимать ссылки на массивы.
Однако во-первых, эта фича находится в статусе
экспериментальной по крайней мере до 5.18 включительно,
так что полагаться на это поведение push
следует с осторожностью.
А во-вторых, push
не оживляет неопределенные неразыменованные ссылки,
по крайней мере в версии 5.14:
Так что если требуется просто добавить
несколько элементов в массив – стоит использовать
простой push @$arrref, ...
Если же требуется какая-то другая
логика обработки массивов,
и функция, ее реализующая, будет принимать
ссылки на массивы – стоит позаботиться
о том, чтобы неопределенные ссылки корректно оживлялись бы.
С печалью отмечаем, что никто из читателей не заметил
неправильного оживления неинициализированных ссылок
в rpush
:(
Зато Тигран заметил еще одну особенность:
если в скрипте сначала написать вызов rpush
,
и только затем определить саму функцию,
то вокруг аргументов надо поставить скобки:
rpush($arrref, ...)
,
иначе perl воспримет конструкцию как вызов метода объекта
и вылетит с ошибкой Can't call method "f" without a package or object reference
.
Если функция определена в начале скрипта, или импортируется из модуля – скобки необязательны.