Rahul Reddy
Rahul Reddy

Reputation: 13173

Perl function protoypes

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

Answers (2)

amon
amon

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.

Example: 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:

  • The * takes a bareword, glob, globref or scalar. E.g. STDOUT or my $fh.
  • The ; makes the following arguments optional.
  • The $ evaluates the next item in scalar context. We'll see in a minute why this is good.
  • The @ 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). Useless
  • open 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.

Example: 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.

Example 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

On backslash prototypes

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

Conclusion

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

Matteo
Matteo

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

Related Questions