Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a Perl subroutine that accepts a block of code

Tags:

I have a set of subroutines that look like this:

sub foo_1($) {   my $name = shift;   my $f;     run_something();   open($f, $name) or die ("Couldn't open $name");   while (<$f>) {     //Something for foo_1()   }   close($f);    do_something_else();  } 

And I have like four or more that look the same, the only thing that changes is the body of the while block. I'd like to abstract this and stop copy-pasting the code around.

  • Is there a way to code a subroutine that accepts a block of code and executes it?

To give more context, the different foo subroutines are a different Finite State Machine (FSM) that read the contents of different files and feed the data to a hash reference. Maybe there is a more intelligent thing to do than what I am trying to accomplish.

like image 270
Ivan Salazar Avatar asked May 23 '11 17:05

Ivan Salazar


People also ask

How do you pass a parameter to a subroutine?

To pass parameters to a subroutine, the calling program pushes them on the stack in the reverse order so that the last parameter to pass is the first one pushed, and the first parameter to pass is the last one pushed. This way the first parameter is on top of the stack and the last one is at the bottom of the stack.


2 Answers

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'); 
like image 165
Eric Strom Avatar answered Sep 21 '22 15:09

Eric Strom


sub bar {    my ($coderef) = @_;    ⁝    $coderef->($f, @arguments);    ⁝ }  bar(sub { my ($f) = @_; while … }, @other_arguments); 

or perhaps a bit less tangled with a named coderef:

my $while_sub = sub {     my ($f) = @_;     while …     ⁝ }; bar($while_sub, @other_arguments); 

Edit: The Higher-Order Perl book is full of this sort of programming.

like image 30
daxim Avatar answered Sep 19 '22 15:09

daxim