问题
I have one function sub _where(\@ \&)
which takes 2 arguments: the first is an array, and the second should be another function. This other function returns a boolean value, and I want to call it inside my for loop of sub _where(\@ \&)
function.
I am having trouble extracting the function I am passing in into a custom local name. I think I do need some local name for it, because it should be possible to pass different boolean functions to my where
function.
where:
sub _where(\@ \&)
{
my @stud = @{$_[0]};
my $student;
my $function = shift;
my $bool = 0;
my $i;
for $i(0..$#stud)
{
my $student = $stud[$i];
function $student;
}
}
Function1 that should be passed:
sub name_starts_with($)
{
my $letter = 'B';
my $student = shift;
my $first;
$first = substr($student -> name, 0, 1);
if($first eq $letter)
{
return 1;
}
}
Function2 that should be passed to where
:
sub points_greater_than($)
{
my $sum_pts = 5;
my $student = shift;
my $pts;
$pts = $student -> points;
if($pts > $sum_pts)
{
return 1;
}
}
Hope you guys could help me out here. Cheers
回答1:
You have bug in argument handling in function _where
. You are putting array reference into $function
variable. You have to do
my @stud = @{shift()};
my $student;
my $function = shift();
or
my @stud = @{$_[0]};
my $student;
my $function = $_[1];
or which I would prefer
sub _where(\@ \&)
{
my ($stud, $function) = @_;
for my $student (@$stud)
{
$function->($student);
}
}
but don't mix those methods.
回答2:
You shouldn't use prototypes. They work differently in Perl from other languages and are almost never a good choice.
You should also avoid making a local copy of the passed-in array unless you want to modify it without affecting the external data.
Finally, a subroutine name beginning with an underscore usually indicates that it is a private method of a class. It doesn't look like that's the case here.
Your code should look like this
sub _where {
my ($stud, $function) = @_;
my $student;
my $bool = 0;
for my $i (0..$#stud) {
my $student = $stud->[$i];
$function->($student);
}
}
Then you can call it as
_where(\@student, \&function);
回答3:
One problem is in how you get parameters:
my @stud = @{$_[0]}; # <-- this doesn't remove first parameter from list
my $student;
my $function = shift; # <-- therefore you'll still get first parameter, not second
Try this fix:
my $function = $_[1]; # always get second parameter
Update
Adding example of how to pass reference to function into other function:
_where(\@stud, \&name_starts_with);
回答4:
After you fix the problem with grabbing the first argument, here are three ways to call a subroutine from a code reference:
&$function($student); # uses the fewest characters!
&{$function}($student); # the style you're using for the array ref
$function->($student); # my favorite style
You can find a lot more detailed information by reading the perlref man page.
回答5:
You seem to be trying to write another language in Perl. Ick. Try this:
sub _where
{
my $students = shift;
my $function = shift;
$function->($_) for @$students;
}
sub name_starts_with
{
my $student = shift;
my $letter = 'B';
my $first = substr($student->name, 0, 1);
return $first eq $letter; # same as 'return $first eq $letter ? 1 : undef;'
}
sub points_greater_than
{
my $student = shift;
my $sum_pts = 5;
my $pts = $student->points;
return $pts > $sum_pts;
}
And you would call it like _where(\@students, \&name_starts_with)
.
But I'm not exactly what the purpose of your _where function is, as it does not return anything (except the last statement evaluated, which doesn't seem too useful in this context).
Maybe you just want grep?
my @students_b = grep { substr($_->name, 0, 1) eq 'B' } @students;
回答6:
If you change the order of the arguments so that the coderef is first, your code will be a little bit more Perlish.
sub _where(\&@){
my $func = shift;
my @return;
for(@_){
push @return, $_ if $func->($_);
}
return @return;
}
If you were well versed in Perl, you would notice that I just re-implemented grep (poorly).
sub name_starts_with{
'B' eq substr($_->name, 0, 1);
}
sub points_greater_than{
$_->points > 5;
}
my @b_students = _where( &name_starts_with, @students );
my $count_of_students_above_5 = _where( &points_greater_than, @students );
Since those subroutines now rely on $_, we should just use grep.
my @b_students = grep( &name_starts_with, @students );
my $count_of_students_above_5 = grep( &points_greater_than, @students );
Since those subroutines are also very short, how about just using a block.
my @b_students = grep {
'B' eq substr($_->name, 0, 1)
} @students;
my $count_of_students_above_5 = grep {
$_->points > 5;
} @students;
来源:https://stackoverflow.com/questions/15914308/passing-one-subroutine-to-another-subroutine