tags:

views:

133

answers:

3

How would I write a function that accepts something like the map function does?

Example:

$func = sub { print $_[0], "hi\n" };
&something($f);
sub something
{
    my $func = shift;
    for ($i = 0; $i < 5; $i++)
    {    $func->($i);    }
}

works fine.

but then if I did

&something( { print $_[0], "hi\n" } );

it wont work and says func is an undefined reference.

So my question would be how would I write a function that accepts parameters like perls map function?

map { s/a/b/g } @somelist;
+1  A: 
&something( { print $_[0], "hi\n" } );
#           ^^^ this isn't a reference

&something( sub { print $_[0], "hi\n" } ); # works just fine
moonshadow
Brad Gilbert
Dave Sherohman
+17  A: 

The map function has very magical syntax, and you probably don't want to copy it unless you have a really good reason; just use a regular anonymous sub, like this:

something(sub { print $_[0], "hi\n" });

If you really want to do this, though, you need to use a prototype:

sub my_map (&@) {
    my ($func, @values) = @_;
    my @ret;
    for (@values) {
        push @ret, $func->($_);
    }
    return @ret;
}

my @values = my_map { $_ + 1 } qw(1 2 3 4);
print "@values";  # 2 3 4 5

(Note that $_ is dynamically scoped, so whatever value it has in the caller is preserved in the function.)

List::Util and List::MoreUtils do this sort of thing a lot to create functions that look built-in and act like variants of map/grep. That's really the only case where something like this should be used.

Eevee
+1. I like the idea of localising $_ with the "for" statement -- allows 1-arg funcrefs to be nice and concise. (And I suppose you could "local ($a, $b);" for 2-arg funcrefs a la sort().)
j_random_hacker
+6  A: 

First off, do not use & when calling subs. From perldoc perlsub:

Subroutines may be called recursively. If a subroutine is called using the & form, the argument list is optional, and if omitted, no @_ array is set up for the subroutine: the @_ array at the time of the call is visible to subroutine instead. This is an efficiency mechanism that new users may wish to avoid.

If you want to be able pass a plain block to "sub something", you need to use a prototype as in:

sub something(&@);

# later

sub something(&@) {
    my ($coderef, @args) = @_;

}

See Prototypes.

I personally would just pass an explicit subref:

something( sub { } );
Sinan Ünür