Reputation: 13173
Why do we use function protoypes in Perl? What are the different prototypes available? How to use them?
Example: $$,$@,\@@
what do they mean?
Upvotes: 5
Views: 204
Reputation: 57640
To write some functions, prototypes are absolutely neccessary, as they change the way arguments are passed, the sub invocations are parsed, and in what context the arguments are evaluated.
Below are discussions on prototypes with the builtins open
and bless
, as well as the effect on user-written code like a fold_left
subroutine. I come to the conclusion that there are a few scenarios where they are useful, but they are generally not a good mechanism to cope with signatures.
CORE::open
Some builtin functions have prototypes, e.g open
. You can get the prototype of any function like say prototype "CORE::open"
. We get *;$@
. This means:
*
takes a bareword, glob, globref or scalar. E.g. STDOUT
or my $fh
.;
makes the following arguments optional.$
evaluates the next item in scalar context. We'll see in a minute why this is good.@
allows any number of arguments.This allows invocations like
open FOO;
(very bad style, equivalent to open FOO, our $FOO
)open my $fh, @array;
, which parses as open my $fh, scalar(@array)
. Uselessopen my $fh, "<foo.txt";
(bad style, allows shell injection)open my $fh, "<", "foo.txt";
(good three-arg-open)open my $fh, "-|", @command;
(now @command
is evaluated in list context, i.e. is flattened)So why should the second argument have scalar context? (1) either you use traditional two-arg-open. Then it isn't difficult to access the first element. (2) Or you want 3-arg-open (rather: multiarg). Then having an explicit mode in the source code is neccessary, which is good style and reduces action at a distance. So this forces you to decide between the outdated flexible 2-arg or the safe multi-arg.
Further restrictions, like that the <
mode can only take one filename, while -|
takes at least one string (the command) plus any number of arguments, are implemented on a non-syntactic level.
CORE::bless
Another interesting example is the bless
function. Its prototype is $;$
. I.e. takes one or two scalars.
This allows bless $self;
(blesses into current package), or the better bless $self, $class
. However, my @array = ($self, $class); bless @array
does not work, as scalar context is imposed on the first arg. So the first argument is not a reference, but the number 2
. This reduces action at a distance, and fails rather than providing a probably wrong interpretation: both bless $array[0], $array[1]
or bless \@array
could have been meant here. So prototypes help and augment input validation, but are no substitute for it.
fold_left
Let us define a function fold_left
that takes a list and an action as arguments. It performs this action on the first two values of the list, and replaces them with the result. This loops until only one element, the return value is left.
Simple implementation:
sub fold_left {
my $code = shift;
while ($#_) { # loop while more than one element
my ($x, $y) = splice @_, 0, 2;
unshift @_, $code->($x, $y);
}
return $_[0];
}
This can be called like
my $sum = fold_left sub{ $_[0] + $_[1] }, 1 .. 10;
my $str = fold_left sub{ "$_[0] $_[1]" }, 1 .. 10;
my $undef = fold_left;
my $runtime_error = fold_left \"foo", 1..10;
But this is unsatisfactory: we know that the first argument is a sub, so the sub
keyword is redundant. Also, We can call it without a sub, which we want to be illegal. With prototypes, we can work around that:
sub fold_left (&@) { ... }
The &
states that we'll take a coderef. If this is the first argument, this allows the sub
keyword and the comma after the sub block to be omitted. Now we can do
my $sum = fold_left { $_[0] + $_[1] } 1 .. 10; # aka List::Util::sum(1..10);
my $str = fold_left { "$_[0] $_[1]" } 1 .. 10; # aka join " ", 1..10;
my $compile_error1 = fold_left; # ERROR: not enough arguments
my $compile_error2 = fold_left "foo", 1..10; # ERROR: type of arg 1 must be sub{} or block.
which is reminiscent of map {...} @list
Backslash prototypes allow to capture typed references to arguments without imposing context. This is good when we want to pass an array without flattening it. E.g.
sub mypush (\@@) {
my ($arrayref, @push_these) = @_;
my $len = @$arrayref;
@$arrayref[$len .. $len + $#push_these] = @push_these;
}
my @array;
mypush @array, 1, 2, 3;
You can think of the \
protecting the @
like in regexes, thus requiring a literal @
character on the argument. This is where prototypes are a sad story: Requiring literal characters is a bad idea. We can't even pass a reference directly, we have to dereference it first:
my $array = [];
mypush @$array, 1, 2, 3;
even though the called code sees and wants exactly that reference. From v14 on, the +
can be used instead. It accepts an array, arrayref, hash or hashref (actually, it's like $
on scalar arguments, and \[@%]
on hashes and arrays). This proto does no type validation, It'll just make sure you receive a reference unless the argument already is scalar.
sub mypush (+@) { ... }
my @array;
mypush @array, 1, 2, 3;
my $array_ref = [];
mypush $array_ref, 1, 2, 3; # works as well! yay
my %hash;
mypush %hash, 1, 2, 3; # syntactically legal, but will throw fatal on dereferencing.
mypush "foo", 1, 2, 3; # ditto
Prototypes are a great way to bend Perl to your will. Recently I was investigating how pattern matching from functional languages can be implemented in Perl. The match
itself has the prototype $%
(one scalar thing which is to be matched, and an even number of further arguments. These are pairs of patterns and code).
They are also a great way to shoot yourself in the foot, and can be downright ugly. From List::MoreUtils
:
sub each_array (\@;\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@\@) {
return each_arrayref(@_);
}
This allows you to call it as each_array @a, @b, @c ...
, but it isn't much effort to directly do each_arrayref \@a, \@b, \@c, ...
, which imposes no limit on the number of parameters, and is more flexible.
Especially parameters like sub foo ($$$$$$;$$)
indicate a code smell, and that you should move to named parameters, Method::Signatures, or Params::Validate.
In my experience, good prototypes are
@
, %
to slurp any (or an even) number of args. Note that @
as sole prototype is equivalent to no prototype at all.&
leading codeblocks for nicer syntax.$
iff you need to pad a slurpy @
or %
, but not on their own.I actively dislike \@
etc, and have yet to see a good use for _
aside from length
(_
can be the last required argument in a prototype. If no explicit value is given, $_
is used.)
Having a good documentation and requiring the user of your subs to include the occasional backslash before your arguments is generally preferable to unexpected action at a distance or having scalar context imposed surprisingly.
Prototypes can be overridden like &foo(@args)
, and aren't honoured on method calls, so they are already useless here.
Upvotes: 7
Reputation: 14940
You can find the description in the official documentation: http://perldoc.perl.org/perlsub.html#Prototypes
But more important: read why you should not use function prototytpes" Why are Perl 5's function prototypes bad?
Upvotes: 8