Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

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

by DamnDirtyApe (Curate)
on Jun 28, 2002 at 05:51 UTC ( [id://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

Replies are listed 'Best First'.
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 ariels (Curate) on Jun 28, 2002 at 05:54 UTC
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
        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.
        A reply falls below the community's threshold of quality. You may see it by logging in.
        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

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
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?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (5)
As of 2024-04-19 02:47 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found