Как бы я сделал эквивалент Enumerator.detect Prototype в Perl с наименьшим количеством кода?

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

Прототип имеет функцию обнаружения для перечислителей, описания которых просто так:

Enumerator.detect(iterator[, context]) -> firstElement | undefined
Finds the first element for which the iterator returns true.

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

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

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

my @result = map function @array;

3 ответа

Решение

Вы говорите, что не хотите модуль, но это именно то, что first функция в List:: Util делает. Это основной модуль, поэтому он должен быть доступен везде.

use List::Util qw(first);
my $first = first { some condition } @array;

Если вы настаиваете на том, чтобы не использовать модуль, вы можете скопировать реализацию из List::Util. Если бы кто-то знал более быстрый способ сделать это, он был бы там. (Обратите внимание, что List:: Util включает реализацию XS, так что это, вероятно, быстрее, чем любой подход с чистым Perl. У него также есть версия с чистым Perl first, в списке::Util::PP.)

Обратите внимание, что проверяемое значение передается подпрограмме в $_ а не как параметр. Это удобно, когда вы используете first { some condition} @values форма, но это то, что вы должны помнить, если вы используете обычную подпрограмму. Еще несколько примеров:

use 5.010; # I want to use 'say'; nothing else here is 5.10 specific
use List::Util qw(first);

say first { $_ > 3 } 1 .. 10;  # prints 4

sub wanted { $_ > 4 }; # note we're using $_ not $_[0]
say first \&wanted, 1 .. 10;   # prints 5

my $want = \&wanted;         # Get a subroutine reference
say first \&$want, 1 .. 10;  # This is how you pass a reference in a scalar

# someFunc expects a parameter instead of looking at $_
say first { someFunc($_) } 1 .. 10; 

Не проверено, поскольку у меня нет Perl на этой машине, но:

sub first(\&@) {
    my $pred = shift;
    die "First argument to "first" must be a sub" unless ref $pred eq 'CODE';
    for my $val (@_) {
       return $val if $pred->($val);
    }
    return undef;
}

Тогда используйте это как:

my $first = first { sub performing test } @list;

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

Так как его здесь нет, определение Perl первой функции, которая локализует $_ для своего блока:

sub first (&@) {
    my $code = shift;
    for (@_) {return $_ if $code->()}
    undef
}

my @array = 1 .. 10;
say first {$_ > 5} @array; # prints 6

Хотя он будет работать нормально, я не рекомендую использовать эту версию, так как List::Util основной модуль (установлен по умолчанию), и его реализация first обычно будет использовать версию XS (написанную на C), которая намного быстрее.

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