Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

How do I write subs that take bare blocks as args?

by DamnDirtyApe (Curate)
on Jun 28, 2002 at 05:51 UTC ( #177940=perlquestion: print w/ replies, xml ) Need Help??
DamnDirtyApe has asked for the wisdom of the Perl Monks concerning the following question:

Fellow Monks;

Can someone please tell me how I can write a function that accepts a bare block as an argument, like map? I want to write a function that's called like this:

if ( each_item { $_->price <= 1000 } @product_list ) { #... }

So far, this is the closest I've been able to get:

sub each_item { my $test = shift ; foreach ( @_ ) { return 0 unless ( eval $test ) } return 1 ; } if ( each_item '$_->price <= 1000', @product_list ) { #... }

I've tried the Camel Book, the Ram Book, Google's thepen search, Super Search, perlfaq, perlsub... I just can't search anymore. I'd really appreciate a hand.

Thanks,


_______________
D a m n D i r t y A p e
Home Node | Email

Comment on How do I write subs that take bare blocks as args?
Select or Download Code
Re: How do I write subs that take bare blocks as args?
by ariels (Curate) on Jun 28, 2002 at 05:54 UTC
Re: How do I write subs that take bare blocks as args?
by kvale (Monsignor) on Jun 28, 2002 at 06:02 UTC
    Use a prototype. Here is an example from Graham Barr's List::Util:
    sub first (&@) { my $code = shift; foreach (@_) { return $_ if &{$code}(); } undef; }
    which is invoked as
    # first defined value in @list $foo = first { defined($_) } @list;
    -Mark
Re: How do I write subs that take bare blocks as args?
by DamnDirtyApe (Curate) on Jun 28, 2002 at 06:20 UTC

    Thank you both. I've found the relevant sections now in perlsub and in the Camel book. For the record, it was as easy as:

    sub each_item (&@) { my $test = shift ; foreach ( @_ ) { return 0 unless ( eval &$test ) } return 1 ; }

    Thanks again.

    Update: On reading kvale's node a little closer, I tried changing my code to:

    sub each_item (&@) { my $test = shift ; foreach ( @_ ) { return 0 unless &{$test} } return 1 ; }

    ...which also worked. Is there any advantage/disadvantage to this over eval?


    _______________
    D a m n D i r t y A p e
    Home Node | Email
      ..which also worked. Is there any advantage/disadvantage to this over eval?
      The eval version probably isn't doing what you want, unless you are doing something deliciously perverse. I think your original code is doing this:
      # "$x = eval &$foo" becomes: my $tmp = &$foo(@_); $x = eval $tmp;
      So you're passing @_ to the user's block, then eval'ing the result. This ends up working because the block presumably accesses $_ directly, and returns an integer (which evals as itself). But it's an ... interesting way to get the job done. An optimist would say that the eval version is "very general".

      /s

      Among those two calls of &$test they are almost equivalent, and first one will trap any errors, so you can later check $@ variable to see if subroutine died. Like "try" in C++.

      OTOH my personal preference is to call anonymous sub as

      $test->(); $test->('arg1',$arg2);

      Courage, the Cowardly Dog.
      PS. Something fishy is going on there, or my name is Vadim Konovalov. And it's not.

Re: How do I write subs that take bare blocks as args?
by Joost (Canon) on Jun 28, 2002 at 11:18 UTC
    By the way, if you use a & prototype, you can still only use a bare block as the first argument - for the other arguments you have to supply a coderef by using sub { ... }. I have written a module that can optionally bypass this restriction using Filter::Util::Call - you might want to take a look at this.
    -- Joost downtime n. The period during which a system is error-free and immune from user input.
Re: How do I write subs that take bare blocks as args?
by flounder99 (Friar) on Jun 28, 2002 at 11:34 UTC
    Couldn't this be done with a grep?

    if (grep {$_->price <= 1000} @product_list) { # do stuff }

    --

    flounder

      Hey, good idea! Except, to work the same as my example, it needs to be:

      if ( ( grep { $_->price <= 1000 } @product_list ) == @product_list ) { # do stuff }

      _______________
      D a m n D i r t y A p e
      Home Node | Email
        You are right. In my example the { #do stuff } block will get executed if any of the members of @product_list pass the conditions. You want to know if all members pass.

        IIRC won't perl6 have some kind of any and all builtins?

        --

        flounder

        Why so complicated? :)
        if(not grep { $_->price > 1000 } @product_list) { # do stuff }
        Update:
        my @p = (0,2,4,5,2,5,7,7); print "1: none\n" if not grep { $_ > 1000 } @p; push @p, 1002; print "2: none\n" if not grep { $_ > 1000 } @p; =output 1: none
        ____________
        Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://177940]
Approved by kvale
Front-paged by jeffa
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (13)
As of 2015-07-07 12:01 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    The top three priorities of my open tasks are (in descending order of likelihood to be worked on) ...









    Results (88 votes), past polls