Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl - Best practices when sending blocks to subs

Tags:

perl

I'm a big fan of functional programming, so when I discovered block references in Perl I started using them a lot.

However, the functions I've written that take blocks as arguments are written in this style:

sub mygrep (&@) {
    my $code = shift;
    my @result;
    foreach $_ (@_) {
        push(@result, $_) if &$code;
    }
    @result;
}

(From http://perldoc.perl.org/perlsub.html#Prototypes)

In essence, most of my functions set $_ in order for the code block to gain access to data in my sub. I guess my question can be split into three subquestions:

  1. Are there some major pitfalls in this approach?
  2. Is it a better idea to localize $_ before setting it?
  3. Should i use partially applied functions instead?

I'm still a Perl newbie so any answers and suggestions are appreciated - thanks in advance! :)

like image 609
Christoffer Avatar asked Dec 06 '22 18:12

Christoffer


1 Answers

In the code you have written:

sub mygrep (&@) {
    my $code = shift;
    my @result;
    foreach $_ (@_) {
        push(@result, $_) if &$code;
    }
    @result;
}

The foreach loop is implicitly localizing the $_ variable on every loop iteration. It is perfectly safe (and the fastest way to get values into $_ properly).

The only nit that I have with the code above is that every time &$code is executed, it has access to the source argument list, which could cause a bug. You could rewrite the code as follows:

sub mygrep (&@) {
    my $code = shift;
    my @result;
    foreach $_ (splice @_) {
        push(@result, $_) if &$code;  # @_ is empty here
    }
    @result;
}

Here are a few other ways you could write that function:

sub mygrep (&@) {
    my ($code, @result) = shift;
    &$code and push @result, $_ for splice @_;
    @result
}

sub mygrep (&@) {
    my $code = shift;
    # or using grep in our new grep:
    grep &$code, splice @_
}

Each of these examples provides an aliased $_ to its subroutine, with proper localization.

If you are interested in higher order functions, I'd encourage you to take a look at my module List::Gen on CPAN, which provides dozens of higher order functions for manipulating both real and lazy lists.

use List::Gen;

my $list = filter {$_ % 2} <1..>;

# as a lazy array:
say "@$list[0 .. 5]"; # 1 3 5 7 9 11

# as an object:
$list->map('**2')->drop(100)->say(5); # 40401 41209 42025 42849 43681

zip('.' => <a..>, <1..>)->say(5);  # a1 b2 c3 d4 e5
like image 57
Eric Strom Avatar answered Jan 05 '23 06:01

Eric Strom