Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Fly Subroutines on the Fly

by Adam (Vicar)
on Sep 19, 2000 at 04:18 UTC ( [id://33042]=perlquestion: print w/replies, xml ) Need Help??

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

What I want to do is write a subroutine that returns an optimized sub-routine.

I started with a closure that returns a subroutine based on the inputs, but now I want to make the resultant subroutine optimized. This means taking different routes to achieve my goal depending on the inputs. ( For example, I don't want to divide by 2**n, but just right shift >>n instead, when a user input is a power of 2 )

I'm not sure how to do this using a closure (short of a big ugly if-then-else structure) so I tried to create subroutines like this:
sub Half { my $n = shift; my $SubBody = Power_Of_Two( $n ) ? "return \$_[$[] >> Log2( $n );" : "return int( \$_[$[] / $n );"; my $SubRef; eval "$SubRef = sub { $SubBody }"; return $SubRef; }
but Perl didn't like that very much.

Then I was thinking I could do this as a module and imitate however CGI.pm does it, which involves something Dr. Stein called the _make_tag_func and the DefaultClass. I wasn't quite able to follow the good Doctor's code further then that. So much for imitating it.

So I turn to the Monastery and the Monks who dwell within. I wasn't overly concerned with making this sub-routine exceedingly efficient, so much as I was concerned about maintainability and clarity. I was much more concerned with resultant subroutine being efficient (hence all this optimiziation stuff... other wise my closure worked fine.)

Thanks.

Replies are listed 'Best First'.
RE (tilly) 1: Fly Subroutines on the Fly
by tilly (Archbishop) on Sep 19, 2000 at 06:03 UTC
    Your bug is that $SubRef is interpolated into the eval. Oops. But you don't need to do that:
    sub Half { my $n = shift; my $SubBody = Power_Of_Two( $n ) ? "return \$_[$[] >> Log2( $n );" : "return int( \$_[$[] / $n );"; my $code = "sub { $SubBody }"; my $sub = eval $code; die "Code '$code' gave error $@" if $@; return $sub; }
    But given the overhead of entering an anonymous function it is not worth looking for optimizations this way. Instead stop and think about "optional hooks" in your code. Where closures rock is as a way to provide optional hooks when you compile a function without paying any run-time price if the hook was not included. So instead of doing run-time checks for whether debugging (for instance) is on, make the debugging checks be inserted or not when you create the string to eval for the sub.

    As the rule goes, optimize on the level of big design decisions, not faster local constructs.

      "I am only an egg"

      What do you mean by "Where closures rock is as a way to provide optional hooks when you compile a function without paying any run-time price if the hook was not included".

      Could you provide an example of what you mean?

      And why is eval being used here at all? Wouldn't the following be simpler, faster, and easier to maintain?

      sub make_divide # "Half" is a lousy name for a curried divide { my $n = shift; if (Power_Of_Two($n)) { my $log = Log2($n); return sub { return $_[0] >> $log; }; } else { return sub { return int( $_[0] / $n ); }; } }
      That's what I'd call a closure (well, the subroutines returned anyway, make_divide itself isn't one, but it's return values are).

      So, as I so often ask, what am I missing?

        I could imagine a database access object that was actually a blessed coderef to a SQL query.

        You might write the method generally enough that it could return the first appropriate match for the query, or all matches.

        With a closure (which will be blessed), you could bind the query to operate on a single table (or a specific query string or whatever)... and you could choose to return a single value from the query or a listref or a hashref.

        That's a contrived example, yes, but I could imagine using it somewhere.

        You are right that the example given is not one where a closure would be appropriate, which is why I pointed out the overhead of entering a function.

        I don't have the energy to write a real example to show you what I mean, but I can point at a case which would have been helped by it. Download Pod::Parser and in it look for the implementation of parse_paragraph. There are a lot of optional hooks provided, and the way it was done is to pass all of the data through a series of functions then keep on checking flags. But most of the time those functions (like preprocess_line and preprocess_paragraph) don't do anything.

        I once looked at it closely, and couldn't tie out the current API. But if you had an API where you created all of the hooks you wanted then asked it to build a parser, it could then create a function, and then once (and once only) decide to only include lines with optional hooks if you were using it. The result would do the same task several times faster since optional hooks would not be present in the final output. (Without having looked I would suspect that Parse::RecDescent does this.)

        As for why the eval, to do what I describe you would want to build up the string, as you walk through checking once for the hook then including or not including bits of what could be part of your sub, then call eval to have it return the final subroutine for you.

        That's only easier to maintain if your subroutines are as simple as just doing division. But mine arn't, they are actually about a dozen or so lines of code, and only have a few points of optimization. It would be silly to have an IFTE structure returning one of 6 or 7 different possabilities... I would have to apply a simple change to every possability rather then one string. That becomes a maintanance nightmare. Hence the question in the first place... what is the best way to assemble an anonymous subroutine on the fly. (I am also curious how CGI.pm does it, since those subroutines are actually named!)

        Tilly gave an answer as to why my eval was failing, but he then went on to confuse me with his disapproval of my method followed by a description of what I should do (which was exactly what I thought I WAS doing). I am somewhat confused by that, and perhaps someone can enlighten me.

Re: Fly Subroutines on the Fly
by fundflow (Chaplain) on Sep 19, 2000 at 06:28 UTC
    There could still be an interesting point here of taking a perl program and outputing an optimized version or list of suggestions for optimizations.

    (This is of course a tough goal as people in compiler design can attest)

    In the simple case above perl optimizes it in compile time, doesn't it?

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://33042]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (5)
As of 2024-04-23 06:35 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found