Зачем мне использовать анонимные функции Perl вместо именованных? - PullRequest
18 голосов
/ 07 мая 2009

Мне просто любопытно, почему в Perl можно использовать анонимную подпрограмму вместо именованной. Спасибо.

Ответы [ 7 ]

31 голосов
/ 07 мая 2009
  • Вы можете хранить анонимные подпрограммы в массивах, хешах и скалярах.
  • Вы можете построить их во время выполнения
  • Вы можете передавать их в качестве аргументов другим функциям.
  • Вы можете хранить переменные в окружающей области видимости.

Последний пункт, пожалуй, самый важный, потому что это часто самый неожиданный аспект именованных и анонимных подпрограмм в Perl. Пример:

sub outer
{
  my $a = 123;

  sub inner
  {
    print $a, "\n";
  }

  # At this point, $a is 123, so this call should always print 123, right?
  inner();

  $a = 456;
}

outer(); # prints 123
outer(); # prints 456! Surprise!

Но изменить «внутренний» с именованной подпрограммы на ссылку на анонимную подпрограмму, и это работает, - гораздо менее удивительный способ:

sub outer
{
  my $a = 123;

  my $inner = sub
  {
    print $a, "\n";
  };

  # At this point, $a is 123, and since the anonymous subrotine 
  # whose reference is stored in $inner closes over $a in the 
  # "expected" way...
  $inner->();

  $a = 456;
}

# ...we see the "expected" results
outer(); # prints 123
outer(); # prints 123

(Конечно, ожидания у всех разные, поэтому «пугающие цитаты» вокруг «ожидаемые».)

Вот пример использования в реальном коде (хотя следует отметить, что интерфейс File::Find обычно считается плохим - из-за использования глобальных переменных, а не использования анонимных подпрограмм):

sub find_files
{
  my @files;

  my $wanted = sub
  { 
    if($something)
    {
      push @files, $File::Find::name;
    }
  };

  # The find() function called here is imported from File::Find
  find({ wanted => $wanted }, $directory);

  return @files;
}

Передача именованной подпрограммы в качестве значения параметра wanted потребует загрязнения пространства имен подпрограммой, которую можно использовать только один раз, и определения именованной подпрограммы в подпрограмме find_files(). «неожиданное» поведение, продемонстрированное ранее.

15 голосов
/ 07 мая 2009

Обратные звонки и генераторы приходят на ум. Пример:

#!/usr/bin/perl

use strict;
use warnings;

sub generate_multiplier {
    my ($coef) = @_;

    return sub { 
        my ($val) = @_;
        $coef * $val;
    }
}

my $doubler = generate_multiplier(2);
my $tripler = generate_multiplier(3);

for my $i ( 1 .. 10 ) {
    printf "%4d%4d%4d\n", $i, $doubler->($i), $tripler->($i);
}

__END__

C:\Temp> v
    1   2   3
    2   4   6
    3   6   9
    4   8  12
    5  10  15
    6  12  18
    7  14  21
    8  16  24
    9  18  27
   10  20  30
8 голосов
/ 09 мая 2009

Я говорю об анонимных подпрограммах и о том, почему вы используете их в Мастеринг Perl . Короче говоря, вы начинаете думать о поведении как о другой форме данных, точно так же, как вы думаете о строках или числах. Когда вы довольны этой идеей, вы можете делать довольно удивительные вещи, потому что вы можете отодвинуть множество решений на очень поздний срок в программе, и вам не нужно искажать дизайн кода, чтобы заранее справиться с любой ситуацией может появиться

Вы можете написать код, зная, что вы собираетесь запустить подпрограмму, только вы еще не знаете какую. Вы доверяете предыдущим шагам, чтобы решить это для вас. Как только вы это сделаете, вы попадаете на другой уровень программирования и чувствуете, что пишете код для создания своей программы. Некоторые проблемы программирования становятся намного легче решить таким способом.

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

8 голосов
/ 07 мая 2009

«Анонимные» подпрограммы действительно похожи на обычные именованные подпрограммы, за исключением того, что они не привязаны к имени в таблице символов.

sub Foo { stuff() }

BEGIN { *Foo = sub { stuff() } }  # essentially equivalent

Во втором случае создается подпрограмма «аноним», а затем привязывается к имени «Foo» в текущем пространстве имен. Блок BEGIN делает это во время компиляции, так же, как обрабатывается именованная подпрограмма. (Это немного сложнее в том, что в первом случае ему присваивается имя, которое будет отображаться в трассировке стека.)

Анонимные подпрограммы полезны в любое время, когда вы хотите создать функцию во время выполнения. Это особенно хорошо для «замыканий» - функций, которые «запоминают» свой лексический контекст. Например, превращение списка в итератор:

use 5.010;
use strict;
use warnings;

sub make_iterator {
  my @list = @_;
  return sub { shift @list }; # new sub that 'remembers' @list
}

my $iter1 = make_iterator( 0 .. 10 ); 
my $iter2 = make_iterator( 'a' .. 'z' );

say $iter1->();  # '0'
say $iter1->();  # '1'
say $iter2->();  # 'a'

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

4 голосов
/ 07 мая 2009

Каноническим ответом для анонимной процедуры обычно является числовая сортировка массива:

my @sorted_array = sort { $a <=> $b } @array;

{ $a <=> $b } представляет анонимную подпрограмму.

1 голос
/ 07 мая 2009

Первое: суб вещь - это суб. my $ thing = sub ... является вложенной ссылкой, хранящейся в переменной.

Второе: есть небольшая разница в использовании:

use strict;
use warnings;

sub xx {
  my $zz=1;

   sub yy {
      print $zz;
   }
}


perl tmp.pl
Variable "$zz" will not stay shared at tmp.pl line 8.

Измените [sub yy ...] на [my $yy = sub { ...] или [local *yy = sub{ ...], и жалоба исчезнет.

Кроме того, если честно, со ссылками на подпрограммы легче справиться, так же, как @ x = (1,2,3) против $ x = [1, 2, 3].

0 голосов
/ 07 мая 2009

Вот пример из моего переписывания версии Nasm.pl

# jump table to subroutines / variables
my %jump = (
  id     => 'id',
  xid    => 'xid',
  hex_id => 'xid',

  h      => \&h,
  mac    => \&mac,
  sed    => \&sed,
  make   => \&make,
  nsis   => \&nsis,

  perl   => \&perl,
  dump   => \&perl,
  yaml   => \&yaml,
  yml    => \&yaml,
  json   => \&json,
  js     => \&json,

  help   => \&help,
  usage  => sub{
    require Pod::Usage;

    Pod::Usage::pod2usage(
      "run perldoc $0 or pod2text $0 for more information"
    );
  }
);

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

...