Re: my & local


From: Anton Berezin <tobez@plab.ku.dk>
Subject: Re: my & local
Newsgroups: fido7.ru.cgi.perl

On Sun, Dec 05, 1999 at 11:18:15PM +0300, Ilya Rubtsov wrote:

> Мож кто вкратце объяснит как всё на самом деле? В чем разница между my
> и local, и каковы у них области действия?

<h2>local</h2>

Я не знаю как в действительности *реализованы* local-переменные, но для
себя всегда представляю некий стек значений, доступ к головному элементу
которого осуществляется по имени переменной, push на который делает
команда? оператор? функция? local, а pop происходит неявно, по выходу за
пределы блока.  Например:


   #! /usr/bin/perl -w
   use strict;
   use vars '$var';

   $var = 5;

Стек:                +---+
$main::var =======>  | 5 |
                     +---+

   sub s1 {  print "$var\n"; }

   sub s2 {  local $var = 37; s1; }

   s1;

Стек внутри s1:      +---+
$main::var =======>  | 5 |
                     +---+

   s2;

Стек после local:    +----+
$main::var =======>  | 37 |
                     +----+
                     | 5  |
                     +----+
В том числе и внутри s1 вызванного из s2.

   s1;

После возврата из s2 произошёл неявный pop, поэтому:
                     +---+
$main::var =======>  | 5 |
                     +---+

То есть локализация переменной это просто удобный способ сохранить
старое значение, поработать с переменной, и автоматически старое
значение восстановить.  Очень часто это используется, например, при
закачке файла целиком в скаляр:


   { local $/; open AA, "< aa" or die $!;  $aa = <AA>; close AA; }

Вместо гораздо менее изячного:

   my $slash = $/; $/ = undef;
   open AA, "< aa" or die $!;  $aa = <AA>; close AA;
   $/ = $slash;

Локализовать можно только переменную, занесенную в таблицу символов, и
поэтому попытка локализовать my-переменную окончится неудачей:

   $ perl -e 'my $bla = 6; local $bla = 7;'

   Can't localize lexical variable $bla at -e line 1.

Local в perl5 используется очень редко.  Обычно нужно хорошо подумать,
прежде чем его употреблять.  Практически, кроме приведенного выше
примера с $/, мне приходилось применять local только в случае рекурсии,
когда одни и те же переменные должны быть видимы из нескольких функций:


   #! /usr/bin/perl -w
   use strict;
   use vars '$depth';

   $depth = 0;

   sub print_current_depth
   {
      print "$depth ";
   }

   sub foo
   {
      local $depth = $depth + 1;
      foo() if $depth < 10;
      print_current_depth;
   }

   foo;
   print "\n";
   __END__

10 9 8 7 6 5 4 3 2 1


Использование local оправдано еще в одном случае, а именно, если
необходимо создать настоящие вложенные функции.  Подробнее об этом -
дальше по тексту.


<h2>my</h2>

My - это новинка perl5.  My-переменная не попадает в таблицу символов и
имеет лексическую область видимости:


   #! /usr/bin/perl -w
   use strict;
   use vars '$var';  # глобальная

   $var = 'global';

   sub s1 { print "$var "; }

   my $var = 'my-file';

   sub s2 { print "$var "; }

   sub s3
   {
      my $var = 'my-function';
      print "$var ";

      if (1) {
         my $var = 'my-block';
         print "$var ";
         s2;
         s1;
      }
      print "$var ";
      s2;
      s1;
   }

   s3;
   print "\n";
   __END__

my-function my-block my-file global my-function my-file global 


My-переменные просты и понятны, доступ к ним - несколько более быстрый,
чем к переменным, входящим в таблицу символов.  Единственная область,
в которой поведение my-переменных может быть не вполне очевидным,
связана с closures (покрытия? закрытия?  в общем, closures), а также с
`наивными' способами создания вложенных функций a la Pascal.

Эта сложность заключается в том, что при задании новой функции,
именованной либо анонимной, функция будет иметь доступ ко всем
лексическим (my) переменным, которые *существовали во время определения
функции*.  Если функция определятся несколько раз, как часто бывает с
анонимными функциями, то разные `экземпляры' функции будут видеть разные
экземпляры лексических переменных:


   #! /usr/bin/perl -w
   use strict;

   my @names = qw(Click Drag Move Push Pop Zapp);

   sub make_callback
   {
      my $name = shift;
      return sub { print "I am called on $name\n"; }
   }

   my %callback = map { $_ => make_callback($_) } @names;

   for ( sort keys %callback) {
      print "Action: $_;\t";
      $callback{$_}->();
   }
   __END__

Action: Click;  I am called on Click
Action: Drag;   I am called on Drag
Action: Move;   I am called on Move
Action: Pop;    I am called on Pop
Action: Push;   I am called on Push
Action: Zapp;   I am called on Zapp


В этом примере каждая сгенерированная анонимная функция видит свой
собственный экземпляр лексической переменной $name, определенной в
функции make_callback().

Теперь - пример с неправильной реализацией вложенных функций:


   #! /usr/bin/perl -w
   use strict;

   sub level1
   {
      my $value = shift;

      sub level2
      {
         print "2: $value; ";
      }

      print "1: $value; ";
      level2();
   }

   level1( 'A');
   level1( 'B');
   level1( 'C');
   print "\n";
   __END__

1: A; 2: A; 1: B; 2: A; 1: C; 2: A;


Я надеюсь, читатель уже понял, что происходит в этом случае.  Perl не
имеет настоящих вложенных функций, и level2() определяется как
глобальная функция.  Но, по правилам областей видимости, level2() должна
иметь доступ к лексической переменной $value.  Поскольку my $value
создается заново при каждом вызове level1(), а level2() определяется
только лишь единожды, level2() будет `видеть' только одну конкретную
копию my $value, ту, которая в несколько виртуализованном состоянии
(level1 еще ни разу не вызвана!) существовала в момент определения
функции level2.  До первого вызова level1() эта копия имела
неопределённое значение, а после первого вызова стала иметь значение
'A', что мы и видим при прогоне теста.  [Замечу в скобках, что эта
`виртуализация' наглядно показывает, что my имеет как эффекты времени
компиляции, так и эффекты времени выполнения.  Впрочем, если подумать,
то иначе и быть не может.]

В заключение, правильный способ создания вложенных функций в Perl5:


   #! /usr/bin/perl -w
   use strict;

   sub level1
   {
      my $value = shift;

      local *level2 = sub {
         print "2: $value; ";
      };

      print "1: $value; ";
      level2();
   }

   level1( 'A');
   level1( 'B');
   level1( 'C');
   print "\n";
   __END__

1: A; 2: A; 1: B; 2: B; 1: C; 2: C;


HTH & cheers, Anton.

P.S. Архивная копия этой статьи лежит здесь:
        http://www.prima.eu.org/tobez/local_my.html

-- 
C makes it easy to shoot yourself in the foot.  C++ makes that harder
-- but if you succeed you take off your whole leg. -- Chip Salzenberg