Почему этот Perl создает ссылку Not CODE?

Мне нужно удалить метод из таблицы символов Perl во время выполнения. Я попытался сделать это с помощью undef &Square::area, который удаляет функцию, но оставляет после себя некоторые следы. В частности, когда вызывается $square->area(), Perl жалуется, что это «Не ссылка на КОД», а не «Неопределенная подпрограмма и Square :: area called», как я и ожидал.

Вы можете спросить: «Почему это важно? Вы удалили функцию, зачем вы ее вызываете?» Ответ в том, что я не называю это Perl. Square наследуется от Rectangle, и я хочу, чтобы цепочка наследования передавала $square->area в &Rectangle::area, но вместо того, чтобы пропустить Square, где метод не существует, а затем перейти в область Rectangle (), вызов метода завершается с сообщением "Not a CODE reference" . "

Как ни странно, это происходит только тогда, когда & Square :: area была определена назначением typeglob (например, *area = sub {...}). Если функция определена с использованием стандартного sub area {} подхода, код работает должным образом.

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

Вот небольшой пример, иллюстрирующий симптом и контрастирующий с правильным поведением:

#!/usr/bin/env perl
use strict;
use warnings;

# This generates "Not a CODE reference". Why?
sub howdy; *howdy = sub { "Howdy!\n" };
undef &howdy;
eval { howdy };
print $@;

# Undefined subroutine &main::hi called (as expected)
sub hi { "Hi!\n" }
undef &hi;
eval { hi };
print $@;

# Undefined subroutine &main::hello called (as expected)
sub hello; *hello = sub { "Hello!\n" };
undef *hello;
eval { hello };
print $@;

Обновление: с тех пор я решил эту проблему с помощью Package :: Stash (спасибо @Ether), но я все еще не понимаю, почему это вообще происходит. perldoc perlmod говорит:

package main;

sub Some_package::foo { ... } # &foo defined in Some_package

Это просто сокращение для присвоения typeglob во время компиляции:

BEGIN { *Some_package::foo = sub { ... } }

Но похоже, что это не просто сокращение, потому что они вызывают разное поведение после отмены определения функции. Я был бы признателен, если бы кто-нибудь мог сказать мне, является ли это случаем (1) неправильной документации, (2) ошибки в perl или (3) PEBCAK.


person KingPong    schedule 12.01.2011    source источник
comment
Откуда вы взяли, что undef &Square::area удалит подпрограмму? Не атака, просто любопытно ...   -  person Ether    schedule 13.01.2011
comment
От 1_. Они приводят пример undef &mysub;, но он также работает с полными именами.   -  person KingPong    schedule 13.01.2011
comment
Альтернативно вы можете задать вопросы. Почему вы в первую очередь определили & Square :: area? Почему бы вам не [пере] определить & Square :: area, чтобы передать его суперклассу?   -  person JB.    schedule 13.01.2011
comment
@JB: это для тестового устройства, а не для живого кода.   -  person KingPong    schedule 13.01.2011


Ответы (2)


Самостоятельное манипулирование ссылками на таблицы символов обязательно доставит вам неприятности, так как есть много мелких неудобных вещей, которые сложно исправить. К счастью, есть модуль, который делает всю тяжелую работу за вас, Package :: Stash - так что просто вызовите его методы add_package_symbol и remove_package_symbol по мере необходимости.

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

Что касается того, почему ваш подход неверен, давайте взглянем на таблицу символов после удаления ссылки на код:

sub foo { "foo!\n"}
sub howdy; *howdy = sub { "Howdy!\n" };

undef &howdy;
eval { howdy };
print $@;

use Data::Dumper;
no strict 'refs';
print Dumper(\%{"main::"});

отпечатки (сокращенные):

    $VAR1 = {
              'howdy' => *::howdy,
              'foo' => *::foo,
    };

Как видите, слот «привет» все еще присутствует - undefining &howdy на самом деле не делает ничего в достаточной степени. Вам необходимо явно удалить слот глобуса *howdy.

person Ether    schedule 12.01.2011
comment
Спасибо за указатель на Package :: Stash. Я как-то пропустил remove_package_symbol при последнем просмотре. Это то, что мне нужно, в отличие от remove_package_glob, который также сбивает @area и $ area и т. Д. Но хотя это полезная информация, она не отвечает на вопрос, а вместо этого говорит, что это сложный вопрос, не беспокойтесь спрашиваю. - person KingPong; 13.01.2011
comment
@KingPong: перезагрузите страницу; объяснение просто заняло немного больше времени, чтобы написать его. :) - person Ether; 13.01.2011
comment
@Esther: Если вы undef &foo, вы увидите, что он все еще находится в таблице символов, как и howdy (), даже если вы его отменили. Разница в том, что Perl знает, что foo () исчез, предположительно потому, что вы объявили его с помощью sub foo {...}, что, согласно perldoc perlmod, эквивалентно BEGIN { *foo = sub {... } }. - person KingPong; 13.01.2011

Причина, по которой это происходит, заключается именно в том, что вы назначили typeglob.

Когда вы удаляете символ CODE, остальная часть typeglob все еще сохраняется, поэтому, когда вы пытаетесь выполнить howdy, он будет указывать на не CODE часть typeglob.

person DVK    schedule 12.01.2011