Perl offers a system called subroutine prototypes that allow you to write user subs that get parsed in a way similar to the builtin functions. The builtins that you want to emulate are map
, grep
, or sort
which can each take a block as their first argument.
To do that with prototypes, you use sub name (&) {...}
where the &
tells perl that the first argument to the function is either a block (with or without sub
), or a literal subroutine \&mysub
. The (&)
prototype specifies one and only one argument, if you need to pass multiple arguments after the code block, you could write it as (&@)
which means, a code block followed by a list.
sub higher_order_fn (&@) {
my $code = \&{shift @_}; # ensure we have something like CODE
for (@_) {
$code->($_);
}
}
That subroutine will run the passed in block on every element of the passed in list. The \&{shift @_}
looks a bit cryptic, but what it does is shifts off the first element of the list, which should be a code block. The &{...}
dereferences the value as a subroutine (invoking any overloading), and then the \
immediately takes the reference to it. If the value was a CODE ref, then it is returned unchanged. If it was an overloaded object, it is turned into code. If it could not be coerced into CODE, an error is thrown.
To call this subroutine, you would write:
higher_order_fn {$_ * 2} 1, 2, 3;
# or
higher_order_fn(sub {$_ * 2}, 1, 2, 3);
The (&@)
prototype that lets you write the argument as a map
/grep
like block only works when using the higher order function as a function. If you are using it as a method, you should omit the prototype and write it this way:
sub higher_order_method {
my $self = shift;
my $code = \&{shift @_};
...
$code->() for @_;
}
...
$obj->higher_order_method(sub {...}, 'some', 'more', 'args', 'here');
($)
prototype may not mean what you think it means. It does mean "give me one argument", but it also means "impose scalar context on that argument". So if you had an array with one element in it, and calledfoo_1 @array
, thenfoo_1
would be passed the number1
, which is the count of the elements in the array. To actually get the first argument, you would need to call it asfoo_1 $array[0]
. If you did not have the prototype, then you could have called it asfoo_1 @array
and it would have worked properly. – Platform@_ == 1 or die "function takes 1 argument"
at the top of the subroutine. – Platform