1
votes

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

6
Are you getting any error message?Ivan Nevostruev

6 Answers

3
votes

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);
2
votes

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);
1
votes

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.

1
votes

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.

1
votes

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;

1
votes

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;