One of the most common tasks in programming is receiving arguments within a function. In Perl, historically, this task has taken on a number of forms, and it has usually looked like this:
sub foo {
my $arg1 = shift;
my $arg2 = shift;
return $arg1 + $arg2;
}
As many Perlers already know, the shift
statement is just short-hand for shift @_
, which takes the first item off of the special @_
list.
There have been other variations on this theme:
# Named arguments
sub foo {
my %args = @_;
my $arg1 = delete $args{arg1};
}
# Named arguments with a hashref
sub foo {
my ($args) = @_;
my $arg1 = delete $args->{arg1};
}
# Positional arguments in a single assignment
sub foo {
my ($arg1, $arg2) = @_;
}
This is a marvelous amount of flexibility, and many folks have found incredible ways of making this work to their advantage. It is a very different way of doing function arguments, though, and if you've been doing Perl long enough you've already spotted that all of the samples above will have that most beloved of warnings, Use of uninitialized value
.
Of course, you could use a default:
sub foo {
my $foo = shift || 1;
}
but what if you want your caller to get an error if they fail to give you enough arguments? Well, you could use function prototypes, like this:
sub foo($) {
my $foo = shift;
}
If you did it this way, code calling this function as foo()
would get the Not enough arguments for main::foo
error. You then would still be able to handle arguments the same way as you normally would after using the prototype, though.
However, the Perl community has long discouraged the use of prototypes, as the syntax is a little hairy and it's not always clear what the developer was trying to do when they started using them.
Fast-forward to Perl 5.20 and now you have signatures!
Quick note: Brian D Foy did a much better write-up here than I'm going to. I'm only going to tell you why I love it.
With function signatures, I can remove boilerplate (many will know that this is one of the time-honored traditions of Perl programming, dispensing with boilerplate) and instead I can focus on the intention of my code.
sub some_function($a, $b) {
return $a + $b;
}
I can even do this with closures:
my $some_closure = sub ($a, $b) {
return $a + $b;
}
Right; I have a pretty syntax that I like, whoop-dee-doo for me, right? Well, here's why you like it.
Remember that Mojolicious thing that I've ranted about before? That you should be loving right now? Or any of those event-based modules that you've been loving? This is where the rubber and road fall in love and get a puppy together.
# This is pseudo-code that might run!
use Mojo::Promise;
sub long_running_operation() {
state $cached = [];
return (scalar @{$cached} > 0)
? Mojo::Promise->resolve( shift @{$cached} )
: Mojo::Promise->new( sub ($resolve, $reject) {
my $retval = eval {
perform_slow_lookup();
}; if ($@ or not $retval) {
return $reject->($@);
}
return $resolve->($retval);
})->then( sub ($results = []) {
$cached = { map { $_->{name} => $_ } @{$results} };
return Mojo::Promise->resolve( shift @{$cached}; );
});
}
Oh, did I say I was going to make you fall in love with Mojo::Promise
, too? My bad, I should have warned you.
So now, with function signatures you have eliminated a remarkable amount of boilerplate here (about four lines in just this snippet), which reduces your surface area for bugs while also making your code smaller and more expressive.
Without signatures, this code would look something like this:
# This is pseudo-code that might run!
use Mojo::Promise;
sub long_running_operation() {
state $cached = [];
return (scalar @{$cached} > 0)
? Mojo::Promise->resolve( shift @{$cached} )
: Mojo::Promise->new( sub {
my ($resolve, $reject) = @_;
my $retval = eval {
perform_slow_lookup();
}; if ($@ or not $retval) {
return $reject->($@);
}
return $resolve->($retval);
})->then( sub {
my $results = shift || [];
$cached = { map { $_->{name} => $_ } @{$results} };
return Mojo::Promise->resolve( shift @{$cached}; );
});
}
See? It's not the end of the world, but it does take away some of the clarity in the code by adding boilerplate lines that you have to visually parse through in order to see what you're trying to do.
Anyway, that's just what crossed my mind while I'm writing more things to trawl GitHub.
Have a great day!
Top comments (2)
Thanks, so this feature should be available for any Perl on version 5.20 or higher?
Correct. You'll need to
use feature 'signatures';
and disable the warnings, as described here: effectiveperlprogramming.com/2015/...In Perl 7, to my knowledge, signatures will be standard and will generate no warnings.
If you're using
Perl::Critic
, you'll also want tono critic
for prototypes, too.