Learning Perl - Prototypes


Learning Perl - Prototypes


Today we are going to discuss Perl subroutine prototypes, which are a way to enforce a certain structure on the arguments passed to subroutines. Prototypes can help catch errors early and make your code more readable.

In Perl, subroutine prototypes are used to define the expected number and types of arguments that a subroutine should receive. They are specified in the subroutine declaration and can include various modifiers. The prototype is placed immediately after the subroutine name. Here’s a simple example:

sub my_subroutine ($$) {
    my ($arg1, $arg2) = @_;
    print "Argument 1: $arg1\n";
    print "Argument 2: $arg2\n";
}
Enter fullscreen mode Exit fullscreen mode

In this example, the prototype ($$) indicates that my_subroutine expects exactly two scalar arguments. If you call it with the wrong number of arguments, Perl will throw an error.

You can also use prototypes to enforce that a subroutine receives a key-value list or hash. For example:

sub my_hash_subroutine (%) {
    my %args = @_;
    print "Arguments as hash:\n";
    foreach my $key (keys %args) {
        print "$key => $args{$key}\n";
    }
}
Enter fullscreen mode Exit fullscreen mode

In this case, the prototype (%) indicates that my_hash_subroutine expects a hash as its argument. When you call it, you should pass a hash. If you do not for example you pass a odd number of arguments, Perl will again throw an error.

The following table summarises common Perl subroutine prototypes:

Prototype Meaning
'$' Scalar value
'@' Array value (flattens list)
'%' Hash value (flattens list)
'&' Code block (subroutine reference)
'*' Typeglob
';' Separates mandatory and optional arguments
'[$@%&*]' Reference to scalar, array, hash, code, or typeglob
'[]' Optional argument (used in documentation, not in actual prototypes)

Today we will apply this concept in a practical example, we will create a new functional Perl module that exports functions with prototypes to demonstrate there usage. Our module will be a basic statistics module that calculates the min, max, mean and median of a list of numbers. We will also allow them to specify a code reference as the first argument to be used to coerce the subsequent list. Think of it like custom grep or map keywords for basic statistics. Let's start by creating a new distribution using Module::Starter we will call it Stats::Basic.

module-starter --module="Stats::Basic" --author="Your Name" --email="your email"
Enter fullscreen mode Exit fullscreen mode

First lets add a new test file t/01-basic.t to test our module. We will start by accessing functions by namespace and add the exporting at the end. The first function we will implement is min, which will return the minimum value from a list of numbers. Add the following to the test file:

use Test::More;
use Stats::Basic;
my $min = Stats::Basic::min { $_ } 5, 1, 3, 2, 4;
is($min, 1, 'Minimum value is correct');
my $min = Stats::Basic::min { $_->{num} } (
    { num => 5 },
    { num => 1 },
    { num => 3 },
    { num => 2 },
    { num => 4 }
);
is($min, 1, 'Minimum value from hash is correct');

done_testing();
Enter fullscreen mode Exit fullscreen mode

Even some experienced Perl developers may not realise this, but when you define a subroutine prototype with a code reference as an argument, you don't explicitly use the sub keyword before the code reference. Perl automatically recognises that the argument is expected to be a code reference and will automagically figure it out, I find this to be a particularly elegant feature and will make our final implementation cleaner. Now let's implement the min function in our module. Open lib/Stats/Basic.pm and add the following code replacing the function1 placeholder:

=head2 min

Returns the minimum value from a list of numbers or a list of numbers.

    my $min = min { $_ } 5, 1, 3, 2, 4;

=cut

sub min (&@) {
    my ($code, @numbers) = @_;
    @numbers = sort {
        $a <=> $b
    } map { $code->($_) } @numbers;
    return $numbers[0];
}
Enter fullscreen mode Exit fullscreen mode

As you can see we have declared our prototype &@ this signifies to Perl that our function accepts a code reference as the first argument and a list as the second. We use map to iterate over the numbers calling the code reference on each item, then the result of that is passed to sort where we sort in ascending numerical order using $a <=> $b. Finally, we return the first element of the sorted array, which will be the minimum value.

If you now run your tests using prove you will see that our basic tests pass:

prove -lv t/
Enter fullscreen mode Exit fullscreen mode

Next lets extend our test file with tests for the max function.

my $max = Stats::Basic::max { $_ } 5, 1, 3, 2, 4;
is($max, 5, 'Maximum value is correct');
$max = Stats::Basic::max { $_->{num} } (
    { num => 5 },
    { num => 1 },
    { num => 3 },
    { num => 2 },
    { num => 4 }
);
is($max, 5, 'Maximum valus is correct');
Enter fullscreen mode Exit fullscreen mode

Now to implement we take a very similar approach to min but switch the sorting.

=head2 max

Returns the maximum value from a list of numbers or a list of numbers.

    my $max = max { $_ } 5, 1, 3, 2, 4;

=cut

sub max (&@) {
    my ($code, @numbers) = @_;
    @numbers = sort {
        $b <=> $a
    } map { $code->($_) } @numbers;
    return $numbers[0];
}
Enter fullscreen mode Exit fullscreen mode

With that in place run your tests again and all should pass. Next we will add the sum function. Lets first add the tests.

my $sum = Stats::Basic::sum { $_ } 5, 1, 3, 2, 4;
is($sum, 15, 'Sum value is correct');
$sum = Stats::Basic::sum { $_->{num} } (
    { num => 5 },
    { num => 1 },
    { num => 3 },
    { num => 2 },
    { num => 4 }
);
is($sum, 15, 'Sum value is correct');
Enter fullscreen mode Exit fullscreen mode

Now to implement this in lib/Stats/Basic.pm:

=head2 sum

Returns the sum value from a list of numbers.

  my $sum = sum { $_ } 5, 1, 3, 2, 4;

=cut

sub sum (&@) {
  my ($code, @numbers) = @_;
  my $sum = 0;
  map { $sum += $code->($_) } @numbers;
  return $sum;
}
Enter fullscreen mode Exit fullscreen mode

The logic is simple we have again defined the &@ prototype, this time we define a variable $sum initialised to 0, then use map to iterate over the numbers, applying our code reference to each item and adding the result to our running total. Finally, we return the accumulated sum. If you run your tests they will pass once again. The final function we are going to implement is mean. The mean is just the sum divided by the number of items so we should be able to reuse the function we just wrote. Lets first write the tests.

my $mean = Stats::Basic::mean { $_ } 5, 1, 3, 2, 4;
is($mean, 3, 'Mean value is correct');
$mean = Stats::Basic::mean { $_->{num} } (
    { num => 5 },
    { num => 1 },
    { num => 3 },
    { num => 2 },
    { num => 4 }
);
is($mean, 3, 'Mean value is correct');
Enter fullscreen mode Exit fullscreen mode

To implement update your lib/Stats/Basic.pm by adding the following:

=head2 mean

Returns the mean value from a list of numebrs

  my $mean = sum { $_ } 5, 1, 3, 2, 4;

=cut

sub mean (&@) {
    my ($code, @numbers) = @_;
    my $sum = sum {$code->($_)} @numbers;
    return $sum / scalar @numbers;
}
Enter fullscreen mode Exit fullscreen mode

We reuse the sum function but there is a caveat - we can't call it directly with our existing code reference. Instead, we need to create a new code reference that calls our original code reference from within. We then divide the sum by the total number of passed arguments to get the mean.

Now if you run your tests they should all pass and you now have a basic statistic module. The final task we need to complete though is the exporting of our functions so we can access them without the namespace. To do this add the following under the version declaration.

use parent 'Exporter';
our @EXPORT_OK = qw/min max sum mean/;
Enter fullscreen mode Exit fullscreen mode

And that completes this post on subroutine prototypes in Perl. Through our Stats::Basic module, we've demonstrated how prototypes can be used to validate arguments and create functions that feel like natural extensions to the Perl language, similar to built-in keywords like grep and map. In the next post, we'll explore overloading of operators, which will allow you to write custom behaviour for your objects when used with certain operators.

Related Blogs

Learning Perl – Introduction
Perl has long been known as the “duct tape of the Internet,” or "the Swiss Army chainsaw of scripting...
Learning Perl - Variables
I will attempt to explain things in this post in a way that is easy to understand, even for those who...
Learning Perl - Arrays
As stated in the previous post, Perl has three types of variables: scalars, arrays and hashes. Today...
Learning Perl - Hashes
In the last post we covered the basics of arrays, today we will look at hashes in more detail. What...
Learning Perl - Conditional Statements
So far we have covered basic variables in Perl, today we are going to look at how to use these...
Learning Perl - Loops and Iteration
In previous posts, we explored variables, arrays, hashes, and conditional statements in Perl. Now...
Learning Perl - Scalars
Before moving onto more complex topics lets come back to how we represent data in Perl. The most...
Learning Perl - References
In the last post we learnt how to create a reference to a scalar, an array, and a hash. In this post,...
Learning Perl - Ternary Operators
In a previous post, we learned about conditional statements in Perl. The ternary operator is an...
Learning Perl - Subroutines
Subroutines are one of the most important building blocks in programming. They allow you to organise...
Learning Perl - Regular Expressions
Regular expressions also known as regex or regexp, are a powerful way to match patterns in text. Most...
Learning Perl - Modules
A module in Perl is a reusable piece of code that can be included in your scripts to provide...
Learning Perl - CPAN
In the last post I showed you how to create a new module and how to use it in your code. In this post...
Learning Perl - Plain Old Documentation
When you write and program in any language it is good practice to document your code. Each language...
Learning Perl - Testing
In this post we will look at how to test Perl code using the Test::More module. Like documentation,...
Learning Perl - Exporting
In programming, we often want to share functionality across different parts of our code. In Perl,...
Learning Perl - Object Orientation
Object-Oriented Programming (OOP) is a widely used programming paradigm that enables the creation of...
Learning Perl - Inheritance
In the last post we discussed Object Oriented Programming in Perl, focusing on the basics of creating...
Learning Perl - File Handles
In programming file processing is a key skill to master. Files are essential for storing data,...
Learning Perl - Overloading Operators
In the last post we investigated prototype subroutines in Perl. In this post, we will look at...