I love function signatures in Perl, and you should too.

manchicken

Mike Stemle

Posted on October 12, 2020

I love function signatures in Perl, and you should too.

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;
}
Enter fullscreen mode Exit fullscreen mode

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) = @_;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

I can even do this with closures:

my $some_closure = sub ($a, $b) {
  return $a + $b;
}
Enter fullscreen mode Exit fullscreen mode

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}; );
    });
}
Enter fullscreen mode Exit fullscreen mode

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}; );
    });
}
Enter fullscreen mode Exit fullscreen mode

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!

💖 💪 🙅 🚩
manchicken
Mike Stemle

Posted on October 12, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related