http://qs1969.pair.com?node_id=854993

myuserid7 has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I want to make my own subroutine work like grep() or map(), being able to take either a block or an expression as a first argument; and if an expression is given, automatically put an it in an implicit block, just like grep() and map() does The block part is easy; but is it possible to make it do the expression part? For example, how to define mygrep(), such that I can do
mygrep($_ % 2 == 0, 0..10)
just like I can do
grep($_ % 2 == 0, 0..10)

Replies are listed 'Best First'.
Re: make subroutine that takes expression as implicit block as first arg
by LanX (Saint) on Aug 13, 2010 at 19:56 UTC
    > The block part is easy; but is it possible to make it do the expression part?

    no, unfortunately there's no prototype mechanism for the latter.

    You can neither easily allow calling a function (which is a special case of the expression part).

    There was a discussion about this a year ago, I will update a link.

    UPDATE:

    see coderefs and (&) prototypes and The & prototype and code references in scalars..

    Cheers Rolf

Re: make subroutine that takes expression as implicit block as first arg
by kennethk (Abbot) on Aug 13, 2010 at 19:46 UTC
    You can accomplish your task using Prototypes. In fact, from the linked documentation,
    here's a reimplementation of the Perl grep operator:

    sub mygrep (&@) { my $code = shift; my @result; foreach $_ (@_) { push(@result, $_) if &$code; } @result; }

    Update: Your example would be called as: print join ",", mygrep {$_ % 2 == 0} 0..10; Note the deviation from the proposed use case. I'd initially missed the spec for "automatically put [ting] it in an implicit block"

      That won't work, for the proposed case:

      Type of arg 1 to main::mygrep must be block or sub {} (not numeric eq +(==)) at /tmp/mygrep line 7, near "10)" Execution of /tmp/mygrep aborted due to compilation errors.

      Perhaps something like this?

      sub mygrep { my $s = "grep $_[0]"; eval $s; } print for mygrep('$_ % 2 == 0, 0..10'); # :-)

      --
      "Language shapes the way we think, and determines what we can think about."
      -- B. L. Whorf
        I added a use case, as this sort of structure is a bit sensitive - perl apparently interprets the OP proposed use case as an anonymous hash because parentheses are present.

        When I was going through a clever phase, I played a bit with prototypes, and decided they are generally more trouble than they are worth. IMHO, of course.

Re: make subroutine that takes expression as implicit block as first arg
by JavaFan (Canon) on Aug 14, 2010 at 10:19 UTC
    In pure Perl, this isn't possible. However, 5.12 has pluggable keywords; I wouldn't rule it out it's possible by creating a new keyword. Unfortunally, my knowledge about pluggable keywords doesn't extend further than "I know they exist".