Perl subroutine arguments

安稳与你 提交于 2019-11-27 03:20:29

You are wary of the Perl environment because it is quite different from the languages you have come across before.

The people who believe in strong typing and function prototypes will disagree here, but I believe that restrictions like that are rarely useful. Has C really caught you passing the wrong number of parameters to a function often enough to be useful?

It is most common in modern Perl to copy the contents of @_ to a list of lexical scalar variables, so you will often see subroutines starting with

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

that way, all parameters that are passed will be available as elements of @_ ($_[0], $_[1] etc.) while the expected ones are named and appear in $p1 and $p2 (although I hope you understand that those names should be chosen appropriately).

In the particular case that the subroutine is a method, the first parameter is special. In other languages it is self or this, but in Perl it is simply the first parameter in @_ and you may call it what you like. In those circumstances you would see

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

so that the context object (or the name of the class if it is a class method) is extracted into $self (a name assumed by convention) and the rest of the parameters remain in @_ to be accessed either directly or, more usually, copied to local scalar variables as $p1, $p2 etc.

Most often the complaint is that there is no type checking either, so I can pass any scalar I like as a subroutine parameter. As long as use strict and use warnings are in context, even this is generally simple to debug, simply because the operations that the subroutine can perform on one form of scalar are usually illegal on another.

Although it was originally more to do with encapsulation with respect to object-oriented Perl, this quote from Larry Wall is very relevant

Perl doesn't have an infatuation with enforced privacy. It would prefer that you stayed out of its living room because you weren't invited, not because it has a shotgun

C was designed and implemented in the days when it was a major efficiency boost if you could get a faulty program to fail during compilation rather than at run time. That has changed now, although a similar situation has arisen with client-side JavaScript where it actually would be useful to know that the code is wrong before fetching the data from the internet that it has to deal with. Sadly, JavaScript parameter checking is now looser than it should be.


Update

For those who doubt the usefulness of Perl for teaching purposes, I suggest that it is precisely because Perl's mechanisms are so simple and direct that they are ideal for such purposes.

  • When you call a Perl subroutine all of the parameters in the call are aliased in @_. You can use them directly to affect the actual parameters, or copy them to prevent external action

  • If you call a Perl subroutine as a method then the calling object or class is provided as the first parameter. Again, the subroutine (method) can do what it likes with @_

Perl doesn't manage your argument handling for you. Instead, it provides a minimal, flexible abstraction and allows you to write code that fits your needs.

Pass By Reference

By default, Perl sticks an alias to each argument in @_. This implements basic, pass by reference semantics.

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

sub foo { $_[0]++ }

Pass by reference is fast but has the risk of leaking changes to parameter data.

Pass By Copy

If you want pass by copy semantics, you need to make the copies yourself. Two main approaches to handling lists of positional parameters are common in the Perl community:

sub shifty {
    my $foo = shift;
}

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

At my place of employment we do a version of listy:

sub fancy_listy {

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

    die "Extra args" if @bad;
}

Named Parameters

Another common practice is the use of named parameters:

sub named_params {
    my %opt = @_;
}

Some people are happy with just the above. I prefer a more verbose approach:

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 
}

This unpacks named params into variables, allows space for basic validation and default values and enforces that no extra, unknown arguments were passed in.

On Perl Prototypes

Perl's "prototypes" are not prototypes in the normal sense. They only provide compiler hints that allow you to skip parenthesis on function calls. The only reasonable use is to mimic the behavior of built-in functions. You can easily defeat prototype argument checking. In general, DO NOT USE PROTOTYPES. Use them with with care that you would use operator overloading--i.e. sparingly and only to improve readability.

amon

For some reason, Perl likes lists, and dislikes static typing. The @_ array actually opens up a lot of flexibility, because subroutine arguments are passed by reference, and not by value. For example, this allows us to do out-arguments:

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

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

… but this is more of an historic performance hack. Usually, the arguments are “declared” by a list assignment:

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

This makes the semantics of this sub call-by-value, which is usually more desirable. Yes, unused arguments are simply forgotten, and too few arguments do not raise any automatic error – the variables just contain undef. You can add arbitrary validation e.g. by checking the size of @_.


There exist plans to finally make named parameters available in the future, which would look like

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

You can have this syntax today if you install the signatures module. But there is something even better: I can strongly recommend Function::Parameters, which allows syntax like

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

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

This also supports experimental type checks.

Parser extensions FTW!

If you really want to impose stricter parameter checks in Perl, you could look at something like Params::Validate.

Perl does have the prototyping capability for parameter placeholders, that you're kind of used to seeing, but it's often unnecessary.

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

And if you did sub foo($$){...} it would require 2 non-optional arguments (eg foo('bar','baz'))

You can just use:

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

To explicitly limit the number of arguments you can use:

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

If you are reading about perl recently, please read recent perl. You may read Modern Perl book for free as well: free online Modern Perl book

Here are few examples from that book about function signatures:

sub greet_one($name = 'Bruce') { 
    say "Hello, $name!"; 
}
sub greet_all($leader, @everyone) { 
    say "Hello, $leader!"; 
    say "Hi also, $_." for @everyone; 
}
 sub make_nested_hash($name, %pairs) { 
    return { $name => \%pairs }; 
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!