Вложенные подпрограммы и Scoping в Perl

Я пишу Perl довольно долгое время и всегда открываю новые вещи, и я просто столкнулся с чем-то интересным, что у меня нет объяснений и не нашел его в Интернете.

sub a {
   sub b {
     print "In B\n";
   }
}
b();

Почему я могу вызывать b() из-за пределов своей области действия и работает?

Я знаю, что это плохая практика, и я не делаю этого, я использую закрытые и такие для этих случаев, но просто видел это.

Ответы

Ответ 1

Подпрограммы хранятся в глобальном пространстве имен во время компиляции. В вашем примере b(); есть короткая рука для main::b();. Чтобы ограничить видимость функции для области, вам нужно назначить подпрограммы анонимной переменной.

Обе именованные и анонимные подпрограммы могут создавать замыкания, но поскольку именованные подпрограммы только скомпилированы один раз, если вы их в гнездо, они не ведут себя так, как многие ожидают.

use warnings;
sub one {
    my $var = shift;
    sub two {
        print "var: $var\n";
    }
}
one("test");
two();
one("fail");
two();
__END__
output:
Variable "$var" will not stay shared at -e line 5.
var: test
var: test

В Perl допускаются вложенные именованные подпрограммы, но это почти наверняка признак того, что код неправильно делает.

Ответ 2

Следующие отпечатки 123.

sub a {
    $b = 123;
}

a();
print $b, "\n";

Итак, почему вы удивляетесь, что и следующее?

sub a {
    sub b { return 123; }
}

a();
print b(), "\n";

Нигде ни один запрос для $b или &b не будет лексическим. На самом деле вы не можете просить &b быть лексическим (пока).

sub b { ... }

в основном

BEGIN { *b = sub { ... }; }

где *b - запись таблицы символов для $b, @b,... и, конечно, &b. Это означает, что subs принадлежат к пакетам и поэтому могут быть вызваны из любого места в пакете или где угодно вообще, если используется их полное имя (MyPackage::b()).

Ответ 3

"Официальным" способом создания вложенных подпрограмм в perl является использование ключевого слова local. Например:

sub a {
    local *b = sub {
        return 123;
    };
    return b();  # Works as expected
}

b();  # Error: "Undefined subroutine &main::b called at ..."

Страница perldoc perlref имеет следующий пример:

sub outer {
    my $x = $_[0] + 35;
    local *inner = sub { return $x * 19 };
    return $x + inner();
}

"Это имеет интересный эффект создания функции локально для другой функции, что обычно не поддерживается в Perl".

Ответ 4

Подпрограммы определяются во время компиляции и не зависят от области видимости. Другими словами, они не могут быть действительно вложенными. По крайней мере, это не касается их собственных возможностей. После определения они эффективно удаляются из исходного кода.