Perl - лучшие практики при отправке блоков на сабы - PullRequest
4 голосов
/ 12 октября 2011

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

Однако написанные мной функции, принимающие блоки в качестве аргументов, написаны в таком стиле:

sub mygrep (&@) {
    my $code = shift;
    my @result;
    foreach $_ (@_) {
        push(@result, $_) if &$code;
    }
    @result;
}

http://perldoc.perl.org/perlsub.html#Prototypes)

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

  1. Существуют ли какие-либо серьезные подводные камни в этом подходе?
  2. Лучше ли local ize $_ перед его настройкой?
  3. Должен ли я вместо этого использовать частично примененные функции?

Я все ещеНовичок в Perl, поэтому любые ответы и предложения приветствуются - заранее спасибо! :) 1022 *

Ответы [ 4 ]

5 голосов
/ 12 октября 2011

В написанном вами коде:

sub mygrep (&@) {
    my $code = shift;
    my @result;
    foreach $_ (@_) {
        push(@result, $_) if &$code;
    }
    @result;
}

Цикл foreach неявно локализует переменную $_ на каждой итерации цикла.Это совершенно безопасно (и самый быстрый способ правильно ввести значения в $_).

Единственный недостаток, который у меня есть с приведенным выше кодом, заключается в том, что каждый раз, когда &$code выполняется, он имеет доступ ксписок исходных аргументов, который может вызвать ошибку.Вы можете переписать код следующим образом:

sub mygrep (&@) {
    my $code = shift;
    my @result;
    foreach $_ (splice @_) {
        push(@result, $_) if &$code;  # @_ is empty here
    }
    @result;
}

Вот несколько других способов написания этой функции:

sub mygrep (&@) {
    my ($code, @result) = shift;
    &$code and push @result, $_ for splice @_;
    @result
}

sub mygrep (&@) {
    my $code = shift;
    # or using grep in our new grep:
    grep &$code, splice @_
}

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

Если вас интересуют функции более высокого порядка, я бы посоветовал вам взглянуть на мой модуль List :: Gen на CPAN, который предоставляет десяткифункции более высокого порядка для управления как реальными, так и ленивыми списками.

use List::Gen;

my $list = filter {$_ % 2} <1..>;

# as a lazy array:
say "@$list[0 .. 5]"; # 1 3 5 7 9 11

# as an object:
$list->map('**2')->drop(100)->say(5); # 40401 41209 42025 42849 43681

zip('.' => <a..>, <1..>)->say(5);  # a1 b2 c3 d4 e5
4 голосов
/ 12 октября 2011

Как насчет использования $code->($arg)?

sub mygrep (&@) {
    my $code = shift;
    my @result;
    foreach my $arg (@_) {
        push(@result, $arg) if $code->( $arg);
    }
    @result;
}

Я не проверял это, но я бы предположил, что это сработает, и это позволит вам передать дополнительные аргументы $code.

Обновлено: это выглядело забавно, поэтому я пошел и проверил его.Это работает просто отлично, см. Ниже (мне очень не нравятся прототипы, поэтому я удалил его, тем более, что он постоянно жаловался на то, что @a не является ссылкой на массив; - (

#!/usr/bin/perl

use strict;
use warnings;

sub mygrep {
    my $code = shift;
    my @result;
    foreach my $arg (@_) {
        push(@result, $arg) if $code->( $arg);
    }
    @result;
}

my @a= ( 1, 2, 3, 4, 5, 6);
print mygrep( sub { return shift() % 2 }, @a), "\n"; 

И, конечно, главное удовольствиес этой линии мышления также генерировать код;

#!/usr/bin/perl

use strict;
use warnings;

sub mygrep {
    my $code = shift;
    my $filter= shift;
    my @result;
    foreach my $arg (@_) {
        push(@result, $arg) if $code->( $arg);
    }
    @result;
}

my @a= ( 1, 2, 3, 4, 5, 6, 7, 8, 9);
print mygrep( mod_filter( 3), @a), "\n"; 
print mygrep( mod_filter( 4), @a), "\n"; 

sub mod_filter
  { my( $filter)= @_;
    return sub { ! (shift() % $filter) };
  }
3 голосов
/ 13 октября 2011
1. Есть ли в этом подходе некоторые подводные камни?
  1. my $_; с учетом блока скроет ваши изменения в переменной пакета $_. Вы ничего не можете с этим поделать изнутри mygrep.

  2. &$code очень особенный. Вы хотите &$code() или $code->() вместо.

  3. Изменение $_ изменит переданные аргументы на mygrep. Это нежелательно здесь.

2. Лучше ли локализовать $ _ перед установкой?

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

3. Должен ли я вместо этого использовать частично примененные функции?

Я не знаю, что это значит.


Исправлено:

sub mygrep (&@) {
    my $code = shift;
    my @result;
    for (@_) {
       # Create copy so $_ can be modified safely.
       for (my $s = $_) {
          push @result, $_ if $code->();
       }
    }

    return @result;
}

Тем не менее, я думаю, mygrep бессмысленно, поскольку map + grep уже делает то, что вы хотите, легче. Сравнить

mygrep { if ($_ % 2) { ++$_; 1 } else { 0 } } LIST

с

map { $_+1 } grep { $_ % 2 } LIST

Вы даже можете объединить map и grep.

map { $_ % 2 ? $_+1 : () } LIST
3 голосов
/ 12 октября 2011

Абсолютно лучше локализовать $_. Подраздел может изменить значение $_, и эти изменения будут распространяться на вызывающую функцию. Это не проблема в mygrep() случае, но может быть в других.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...