Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister

Choosing between multiple closures

by oakb (Scribe)
on Jun 12, 2014 at 07:22 UTC ( #1089654=perlquestion: print w/replies, xml ) Need Help??
oakb has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to build a large application with the main functionality abstracted out to subroutines. There are two ways of doing an important job, and four different structures against which the job may be run — meaning that there are eight possible job/structure combinations from which the program must choose, based on configuration settings gathered earlier. Also, each of the eight combinations needs to accept many arguments.

Each of the eight combination subroutines accepts a bunch of arguments, structures internal data, performs initial setup actions, and then returns an iterator in the form of an anonymous sub (a big shout-out and thank you to MJD!).

The problem lies in choosing the correct subroutine. I can get each of the subroutines to work by calling it directly from my main code, but every way I've tried to interject the selection process — including dispatch tables, switch statements, etc. — results in the anonymous subroutine (i.e. iterator) not being returned and a "strict refs" error based on calling a sub with an empty string as the only identifier; with "strict refs" turned off, it gives an undefined subroutine error.

This works:
sub increment { my ( $number, $incval ) = @_; return sub { $number += $incval; return $number; } } my $action = increment( 1, 2, 'plus', 'tons', 'more', 'arguments' ); while ( my $result = $action->() ) { print "$result\n"; }
Before you complain, yes, I know that this will print an infinite list of odd numbers; please rest assured that my actual application returns an exhaustible iterator.

This (and many other permutations of it I've tried) does not work, produces error "Can't use string ("") as a subroutine ref while "strict refs" in use at....":
sub do_something { my $action = shift( @_ ); for ( $action ) { when ( /INCREMENT/ ) { \&increment( @_ ) } default { die "You idiot!" } } } sub increment { my ( $number, $incval ) = @_; return sub { $number += $incval; return $number; } } my $action = do_something( 'INCREMENT', 1, 2, 'long', 'list' ); while ( my $result = $action->() ) { # <-- The error happens here print "$result\n"; }
How can I select the correct job/structure subroutine, send all necessary arguments, and get back a usable iterator?

This is my most desperate hour. Help me, Perl Monks. You're my only hope.

Replies are listed 'Best First'.
Re: Choosing between multiple closures
by Eily (Prior) on Jun 12, 2014 at 07:42 UTC

    When you write \&increment( @_ ) you take a reference to the output of the increment sub, which already is a reference. And then, you don't return the result, so your do_something sub returns the result of the for statement, which is not what you expect.

    You can correct your code by replacing \&increment( @_ ) with return increment(@_) (the & isn't actually useful here).

      Thank you, Eily. I just made the change to my actual code, and VOILA! It works.

      It makes perfect sense, too. My problem lay in not understanding the need to return the return — I guess having so many returns is counterintuitive to me.

      UPDATE: In the groggy wee morning hours, I understated the number of returns involved... the fix is actually returning the returned return. Yup, that's three returns, folks. No wonder I couldn't get my brain around it before reading Eily's elucidating answer.

      Even though I thanked Mark Jason Dominus in my OP, it was actually Chap. 2 of his book that originally led me astray, since it shows coderefs everywhere an anonymous subroutine is too large to include right in a dispatch table:
      $dispatch_table = { CHDIR => \&change_dir, LOGFILE => \&open_log_file, VERBOSITY => \&set_verbosity, ... => ..., };
      (Higher-Order Perl, p. 44)

      Of course, Chap. 2 didn't contemplate returning iterators, which don't really make an appearance until Chap. 4, so this is not a condemnation of MJD or his excellent book — without which I wouldn't even be using iterators in the first place. No, the fault is completely mine for mixing concepts MJD didn't intend to combine at that point in the book. Mea culpa.
Re: Choosing between multiple closures
by Anonymous Monk on Jun 12, 2014 at 07:39 UTC

    What do you suppose this does?  when ( /INCREMENT/ ) { \&increment( @_ ) }

    How about  \&increment( @_ )  

    So, if you're trying to return a reference, don't take a reference to that reference, simply return the reference

    return increment( @_ );
Re: Choosing between multiple closures
by Anonymous Monk on Jun 12, 2014 at 14:34 UTC

    Even though you said the issue is solved, I'm curious as to why dispatch tables didn't work, since that's another common approach?

    my %DISPTBL = ( INCREMENT => sub { my ( $number, $incval ) = @_; return sub { ... } }, ); sub do_something { my $action = shift; return $DISPTBL{$action}(@_); } ...

    You could even skip do_something completely and go right to the table...

Re: Choosing between multiple closures
by Anonymous Monk on Jun 12, 2014 at 07:35 UTC
    Can you update your code to be runnable without needing any edits?
      Erm... unless you're talking about extraneous things like the shebang and use strict; — which should never be included here for sake of brevity — the provided code is runnable.

        which should never be included here for sake of brevity

        If you look at How do I post a question effectively? you'll see strict and warnings ... and shebang is just one more line, so keep them for the sake of brevity ... and to avoid followups like "did you forget strict/warnings aka the free help in your real code?"

        No dude , talking about feature

        syntax error at - line 4, near ") {" syntax error at - line 7, near "}" Can't use global @_ in "my" at - line 10, near "= @_" syntax error at - line 15, near "}" Execution of - aborted due to compilation errors.

        But I already eyeballed a solution :)

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1089654]
Approved by Ratazong
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others surveying the Monastery: (4)
As of 2018-04-25 16:54 GMT
Find Nodes?
    Voting Booth?