views:

72

answers:

3

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;
+15  A: 

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; 
cjm
+1 List::Util and List::MoreUtils are very functional -- after all, they even contain a `reduce`. :)
Ether
Thanks for explaining. My main gripe with getting in another module was that my boilerplate was getting a bit much, but at this point I guess I cannot avoid breaking out Toolset.Also, in re your latter explanation, I'll actually be using them in these two forms: my $res = first some_func, @array;Where somefunc is a sub acting on $_; and: my $res = first some_func($_, 5), @array;Where some_func is a sub acting on @_. (At least I hope these will work the same way they do for map.) (Argh, no formatting in comments. :( )
Mithaldu
+5  A: 

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.

Dan
That's pretty much the same as the version in List::Util::PP (except that one doesn't use a lexical variable in the for loop).
cjm
Good to know. Like I said this was totally off the cuff.
Dan
The reason for not using a lexical variable is that it allows the sub to refer to the value being tested as `$_` instead of `$_[0]` (as you'd have to do in your version).
cjm
True, although my impression was that the convention with this type of setup was that it was preferable to do it this way because it reminds the caller that that block really is a subroutine and is not quite analagous to `map` or `grep`. I could be off on that.
Dan
+4  A: 

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.

Eric Strom