Почему этот Perl выдает "Не ссылка на код"?

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

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

Как ни странно, это происходит только тогда, когда &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.

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,
    };

Как вы можете видеть, слот "привет" по-прежнему присутствует - неопределенный &howdy на самом деле ничего не делает. Вы должны явно удалить слот glob, *howdy,

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

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

Другие вопросы по тегам