Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would I do the equivalent of Prototype's Enumerator.detect in Perl with the least amount of code?

Lately I've been thinking a lot about functional programming. Perl offers quite a few tools to go that way, however there's something I haven't been able to find yet.

Prototype has the function detect for enumerators, the descriptions is simply this:

Enumerator.detect(iterator[, context]) -> firstElement | undefined
Finds the first element for which the iterator returns true.

Enumerator in this case is any list while iterator is a reference to a function, which is applied in turn on each element of the list.

I am looking for something like this to apply in situations where performance is important, i.e. when stopping upon encountering a match saves time by disregarding the rest of the list.

I am also looking for a solution that would not involve loading any extra module, so if possible it should be done with builtins only. And if possible, it should be as concise as this for example:

my @result = map function @array;
like image 348
Mithaldu Avatar asked Jan 18 '10 22:01

Mithaldu


3 Answers

You say you don't want a module, but this is exactly what the first function in List::Util does. That's a core module, so it should be available everywhere.

use List::Util qw(first);
my $first = first { some condition } @array;

If you insist on not using a module, you could copy the implementation out of List::Util. If somebody knew a faster way to do it, it would be in there. (Note that List::Util includes an XS implementation, so that's probably faster than any pure-Perl approach. It also has a pure-Perl version of first, in List::Util::PP.)

Note that the value being tested is passed to the subroutine in $_ and not as a parameter. This is a convenience when you're using the first { some condition} @values form, but is something you have to remember if you're using a regular subroutine. Some more examples:

use 5.010; # I want to use 'say'; nothing else here is 5.10 specific
use List::Util qw(first);

say first { $_ > 3 } 1 .. 10;  # prints 4

sub wanted { $_ > 4 }; # note we're using $_ not $_[0]
say first \&wanted, 1 .. 10;   # prints 5

my $want = \&wanted;         # Get a subroutine reference
say first \&$want, 1 .. 10;  # This is how you pass a reference in a scalar

# someFunc expects a parameter instead of looking at $_
say first { someFunc($_) } 1 .. 10; 
like image 50
cjm Avatar answered Oct 21 '22 05:10

cjm


Untested since I don't have Perl on this machine, but:

sub first(\&@) {
    my $pred = shift;
    die "First argument to "first" must be a sub" unless ref $pred eq 'CODE';
    for my $val (@_) {
       return $val if $pred->($val);
    }
    return undef;
}

Then use it as:

my $first = first { sub performing test } @list;

Note that this doesn't distinguish between no matches in the list and one of the elements in the list being an undefined value and having that match.

like image 24
Dan Avatar answered Oct 21 '22 03:10

Dan


Just since its not here, a Perl function definition of first that localizes $_ for its block:

sub first (&@) {
    my $code = shift;
    for (@_) {return $_ if $code->()}
    undef
}

my @array = 1 .. 10;
say first {$_ > 5} @array; # prints 6

While it will work fine, I don't advocate using this version, since List::Util is a core module (installed by default), and its implementation of first will usually use the XS version (written in C) which is much faster.

like image 24
Eric Strom Avatar answered Oct 21 '22 05:10

Eric Strom