http://qs321.pair.com?node_id=1207304

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

Hi Monks,

I'd like to make a return from subroutine by the code running in a BLOCK.

I noticed how grep is working in the question:

sub working { my @a = (1); grep { print "grep\n"; return 'leave' } @a; print "Still here #1\n"; } working; __DATA__ grep

So, "Still here #1" is never shown.

grep is a Perl function, but I'd like to get such behaviour in a BLOCK of my own subroutine. Below is my example:

sub mygrep (&@) { my $code = shift; my @result; foreach $_ (@_) { push(@result, $_) if &$code; } @result; } sub notworking { my @a = (1); mygrep { print "mygrep\n"; return 'leave' } @a; print "Still here #2\n"; } notworking; __DATA__ mygrep Still here #2

And this is nothing like original grep makes.

So, the question is: How to run a code in BLOCK passed to a subroutine and call return in a caller?

Replies are listed 'Best First'.
Re: Bare BLOCK vrs. grep BLOCK
by AnomalousMonk (Archbishop) on Jan 16, 2018 at 01:03 UTC

    The  (&@) prototyping mechanism is intended allow user-defined functions to enjoy an approximation of some of the behavior of built-in functions like grep and map, but does not allow exact duplication of all such behavior. I've never tried it myself, but I imagine that if you wanted to do this trick, you'd have to look at the source of the built-in you wanted to emulate and figure out how it achieves its Deep Magick, and then roll your own with, e.g., an Inline::C definition that duplicates the incantations.

    Perhaps some much more subtle monk than I can comment further.


    Give a man a fish:  <%-{-{-{-<

Re: Bare BLOCK vrs. grep BLOCK
by kcott (Archbishop) on Jan 16, 2018 at 10:26 UTC

    G'day powerin,

    Welcome to the Monastery.

    Firstly, here's a cut-down version of what you're doing. This is just to make subsequent examples more succinct.

    $ perl -E 'sub W { grep { say "x"; return "z" } 1; say "y" } W' x
    "So, "Still here #1" is never shown."

    No, it won't be. You're returning from &working before the print statement is reached. You can confirm this by checking the return value.

    $ perl -E 'sub W { grep { say "x"; return "z" } 1; say "y" } say W' x z

    Removing the return statement from the original code gives the result you seem to be after.

    $ perl -E 'sub W { grep { say "x" } 1; say "y" } W' x y

    Calling either the builtin grep, or your custom mygrep, in void context, is highly questionable. I wonder whether this is really what you're trying to do, or if this was maybe just an example of what you want. Perhaps describing your actual intent, possibly with just pseudocode, will elicit better answers.

    — Ken

Re: Bare BLOCK vrs. grep BLOCK
by AnomalousMonk (Archbishop) on Jan 16, 2018 at 02:32 UTC

    Here's something that's rather crass, but maybe something like it will save you from venturing into dark regions:

    c:\@Work\Perl\monks>perl -wMstrict -le "sub working { my @x = (1, 0, 2); my @y = grep { print qq{in grep: '$_'}; return 'byebye'; } @x; print 'still in working(): ', qq{(@y)}; return @y; } my @z = working; print qq{from working(): (@z)}; " in grep: '1' from working(): (byebye) c:\@Work\Perl\monks>perl -wMstrict -le "sub mygrep (&@) { my $code = shift; my @result; for (@_) { no warnings 'exiting'; push(@result, $_) if map { print 'in map: ', qq{'$_'}; $_; } &$code; } MYGREP_FINAL: return @result; } ;; sub notworking { my @x = (4, 0, 3); my @y = mygrep { print qq{in mygrep: '$_'}; goto MYGREP_FINAL; } @x; print 'still in notworking(): ', qq{(@y)}; return @y; } ;; my @z = notworking; print qq{from notworking(): (@z)}; " in mygrep: '4' still in notworking(): () from notworking(): ()


    Give a man a fish:  <%-{-{-{-<

Re: Bare BLOCK vrs. grep BLOCK
by vr (Curate) on Jan 16, 2018 at 17:52 UTC

    As pointed in other answers, the BLOCK as argument to grep is Perl's black magic, but BLOCK as argument to a subroutine prototyped with (&) is just syntactic sugar to allow to omit the sub keyword. So the latter is not "bare BLOCK", but subroutine body, and the return statement returns one frame above, as it should.

    More specifically:

    An & requires an anonymous subroutine, which, if passed as the first argument, does not require the sub keyword or a subsequent comma.

    And:

    return

    Returns from a subroutine, eval, do FILE, sort block or regex eval block (but not a grep or map block)...

    In effect, it's same question, as "Is it possible for a Perl subroutine to force its caller to return?". Following the CPAN links, here's a solution using the Scope::Upper, with a bit convoluted example. Nevertheless, I think it answers your question, i.e. the maybe_working sub doesn't have to take any additional measures for this example to work. The anon sub ALWAYS returns normally, so its the my_grep responsibility to check the return value (so there's reserved return value) and decide, whether to continue or return to 2 stack frames above.

    use strict; use warnings; use feature 'say'; use Scope::Upper qw/ unwind :words /; sub my_grep (&@) { my $code = shift; my @out; for ( @_ ) { say "in @{[ (caller(0))[3] ]}, processing the $_"; my $result = &{ $code }; unwind @out, UP UP if $result eq 'leave'; push @out, $result } return @out; } sub maybe_working { my @out = my_grep { return /4/ ? 'leave' : $_ } 1 .. 5; say 'still here, but you won\'t see this line!'; return @out; } say "in final output: $_" for maybe_working; say 'exiting...';

    Output:

    in main::my_grep, processing the 1 in main::my_grep, processing the 2 in main::my_grep, processing the 3 in main::my_grep, processing the 4 in final output: 1 in final output: 2 in final output: 3 exiting...
Re: Bare BLOCK vrs. grep BLOCK
by ikegami (Patriarch) on Jan 17, 2018 at 04:56 UTC

    You are under the incorrect impression that grep (and other functions) are subroutine calls. They are actually operators or sequence of operators. grep is closer to while than it is to a sub.

    You can plug into the parser to cause foo BLOCK to compile into a custom sequence of ops (kinda like grep does) using Devel::CallParser. Syntax::Feature::Loop is an example of this.

    Syntax::Feature::Loop allows you to do the following:

    use syntax qw( loop ); loop { ... }

    The above snippet is opcode-for-opcode identical to the following:

    while (1) { ... }

    loop BLOCK compiles as a statement (like while), but having it compile as an expression (like grep) is just a question of passing a different flag. So that means it's possible to use Devel::CallParser to make it so

    mygrep BLOCK LIST

    compiles into the same opcodes as

    grep BLOCK LIST

    But that's not quite what you asked for. You asked for run-time evaluation of sequences of opcodes that don't form the body of sub. The whole point of a sub call is that it provides a way of returning to the calling code. But you don't have that in your scenario. You'd have to append an opcode to the chain to tell the interpreter to which op to jump after it reaches the end of the sequence. We're talking about self-modifying code. You're literally asking to get mygrep to replace its own body with the opcodes of the block at run-time. Even Perl doesn't go that far.

Re: Bare BLOCK vrs. grep BLOCK
by pritesh (Scribe) on Jan 15, 2018 at 20:58 UTC
    Hi, may be I am asking a dumb question, but why do you have (&@) in sub mygrep (&@) ? Shouldn't it just be sub mygrep { your greplike function code}?

      In perlsub subroutine prototypes are discussed. They are seldom needed, but a grep-like subroutine is one of the examples, and is shown using &@ as a prototype. This means to expect a subroutine and then a list.