Как бы я сделал эквивалент 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), которая намного быстрее.