Подтвердить что ты не робот

Аргументы подпрограммы Perl

Я недавно читал о Perl и немного озадачен тем, как Perl обрабатывает аргументы, переданные подпрограммам.

В языке, таком как Python, Java или PHP, определение функции принимает форму (в псевдокоде):

function myFunc(arg1, arg2) {
    // Do something with arg1 and arg2 here
}

Однако в Perl это просто:

sub mySub {
    # @_ holds all arguments passed
}

И как я понимаю, это единственный способ сделать это.

  • Что делать, если я хочу ограничить вызывающий объект только двумя аргументами?

  • Разве это не просто Perl, не допускающий ничего, кроме аргументов с переменным числом на других языках (например, Python, C и т.д.)?

  • Разве это не проблема в какой-то момент?

  • Как насчет всех проверочных параметров аргументов по умолчанию на других языках? Нужно ли это делать прямо в Perl? Например,

    sub a_sub {
        if (@_ == 2) {
            # Continue function
        }
        else {
            return false
        }
    }
    
4b9b3361

Ответ 1

Вы опасаетесь среды Perl, потому что она сильно отличается от языков, с которыми вы сталкивались раньше.

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

В современном Perl наиболее часто копируется содержимое @_ в список лексических скалярных переменных, поэтому вы часто увидите подпрограммы, начинающиеся с

sub mysub {
  my ($p1, $p2) = @_;
  ... etc.
}

таким образом, все передаваемые параметры будут доступны как элементы @_ ($_[0], $_[1] и т.д.), в то время как ожидаемые названы и появятся в $p1 и $p2 (хотя я надеюсь, вы понимаете, что эти имена должны быть выбраны надлежащим образом).

В частном случае, когда подпрограмма является методом, первый параметр является особым. В других языках это self или this, но в Perl это просто первый параметр в @_, и вы можете называть его тем, что вам нравится. В этих обстоятельствах вы увидите

sub method {
  my $self = shift;
  my ($p1, $p2) = @_;
  ... etc.
}

чтобы объект контекста (или имя класса, если он является методом класса) извлекается в $self (имя, принятое условным обозначением), а остальные параметры остаются в @_ для доступа либо напрямую или, чаще всего, копируются в локальные скалярные переменные как $p1, $p2 и т.д.

Чаще всего жалоба заключается в том, что проверки типа нет, поэтому я могу передать любой скаляр, который мне нравится, в качестве параметра подпрограммы. Пока use strict и use warnings находятся в контексте, даже это обычно легко отлаживать, просто потому, что операции, которые подпрограмма может выполнять на одной форме скаляра, обычно являются незаконными на другом.

Хотя первоначально это было больше связано с инкапсуляцией относительно объектно-ориентированного Perl, эта цитата из Larry Wall очень актуальна

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

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


Обновление

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

  • Когда вы вызываете подпрограмму Perl, все параметры в вызове псевдонимы в @_. Вы можете использовать их напрямую, чтобы повлиять на фактические параметры, или скопировать их, чтобы предотвратить внешнее действие.

  • Если вы вызываете подпрограмму Perl как метод, тогда вызывающий объект или класс предоставляется в качестве первого параметра. Опять же, подпрограмма (метод) может делать то, что ей нравится, с @_

Ответ 2

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

Передача по ссылке

По умолчанию Perl использует псевдоним для каждого аргумента в @_. Это реализует базовую семантику по ссылке.

my $num = 1;
foo($num);
print "$num\n";  # prints 2.

sub foo { $_[0]++ }

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

Pass By Copy

Если вам нужна семантика передать по копии, вам нужно сделать копии самостоятельно. В сообществе Perl распространены два основных подхода к обработке списков позиционных параметров:

sub shifty {
    my $foo = shift;
}

sub listy {
    my ($foo) = @_;
}

В моем месте работы мы делаем версию listy:

sub fancy_listy {

    my ($positional, $args, @bad) = @_;

    die "Extra args" if @bad;
}

Именованные параметры

Другой распространенной практикой является использование параметров named:

sub named_params {
    my %opt = @_;
}

Некоторые люди довольны только вышеизложенным. Я предпочитаю более подробный подход:

sub named_params {
    my %opt = @_;

    my $named = delete $opt{named} // "default value";
    my $param = delete $opt{param}
        or croak "Missing required 'param'";

    croak "Unknown params:", join ", ", keys %opt
        if %opt;

    # do stuff 
}

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

На Perl Prototypes

Perl "прототипы" - это прототипы не в нормальном смысле. Они предоставляют только подсказки компилятора, которые позволяют пропускать скобки при вызове функций. Единственное разумное применение - имитировать поведение встроенных функций. Вы можете легко победить проверку аргументов прототипа. В общем, НЕ ИСПОЛЬЗУЙТЕ ПРОТОТИПЫ. Используйте их с осторожностью, чтобы использовать перегрузку оператора - т.е. экономно и только для улучшения удобочитаемости.

Ответ 3

По какой-то причине Perl любит списки и не любит статическую типизацию. Массив @_ фактически открывает большую гибкость, потому что аргументы подпрограммы передаются по ссылке, а не по значению. Например, это позволяет нам делать аргументы:

my $x = 40;
add_to($x, 2);
print "$x\n"; # 42

sub add_to { $_[0] += $_[1] }

... но это скорее исторический взлом производительности. Обычно аргументы "объявляются" назначением списка:

sub some_sub {
  my ($foo, $bar) = @_;
  #               ^-- this assignment performs a copy
  ...
}

Это делает семантику этого вспомогательного вызова по значению, что обычно более желательно. Да, неиспользуемые аргументы просто забыты, и слишком мало аргументов не вызывает никакой автоматической ошибки - переменные просто содержат undef. Вы можете добавить произвольную проверку, например. проверив размер @_.


Существуют планы окончательно сделать именованные параметры доступными в будущем, которые будут выглядеть как

sub some_sub($foo, $bar) { ... }

Этот синтаксис можно использовать сегодня, если вы установите модуль signatures. Но есть что-то еще лучше: я могу настоятельно рекомендовать Function::Parameters, что позволяет использовать синтаксис как

fun some_sub($foo, $bar = "default value") { ... }

method some_method($foo, $bar, :$named_parameter, :$named_with_default = 42) {
  # $self is autodeclared in methods
}

Это также поддерживает проверки экспериментального типа.

Расширения Parser FTW!

Ответ 4

Если вы действительно хотите наложить более строгие проверки параметров в Perl, вы можете посмотреть что-то вроде Params:: Validate.

Ответ 5

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

sub foo($){
    say shift;
}; 
foo();      # Error: Not enough arguments for main::foo
foo('bar'); # executes correctly

И если вы сделали sub foo($$){...}, для этого потребовались бы 2 необязательных аргумента (например, foo('bar','baz'))

Ответ 6

Вы можете просто использовать:

my ($arg1, $arg2) = @_;

Чтобы явно ограничить количество аргументов, которые вы можете использовать:

my $number =2;
die "Too many arguments" if @_ > $number;