1

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
  • Are you getting any error message? Commented Apr 10, 2013 at 0:29

6 Answers 6

3

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);
Sign up to request clarification or add additional context in comments.

4 Comments

You should use prototypes when your subroutine expects a code reference.
@mob No, you shouldn't. You should (must, actually) use prototypes when your subroutine expects a BLOCK. There is no need to use prototypes with regular code references, as in the OP's example.
Why didn't you also show how to change that to a foreach loop?
The problem definitely doesn't caused by using prototypes. Yes, it is not clever. Yes, it is useless in this case but code doesn't work just because variable $function doesn't contain function passed as argument in surrounding code.
2

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

Comments

2

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;

Comments

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.

3 Comments

You can successfully mix both methods, but it is a advanced technique that is rarely useful. Example: allowing a subroutine to be used as Module::func() and as Module->func() package Module; sub func{ shift if $_[0] eq __PACKAGE__; ... }
Yes, you can but don't is good advice. I hasn't written you can't. Perl allow you to do many insane thing but you should not do many of this stuff.
@Hynek-Pichi-Vychodil: I wasn't trying to imply that those things were the cause of the problem. In fact the main reason the OP's code doesn't work is because $student isn't a blessed object or a package name in function $student.
1

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.

Comments

1

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;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.