Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??
While trying to embed a domain-specific language into Perl, I came across an interesting problem involving local subroutines. In this meditation, I will build up to the problem and then explain how I solved it. If you can think of a better solution, please let me know.

One trick that is useful in certain limited circumstances is to define (or redefine) a subroutine locally, in effect overriding any original definition that may exist. For example, let's say I have the following subroutines:

use warnings; use strict; sub a { "unchanged" }; sub print_a { print a(), "\n" };
I can temporarily override the definition of a like so:
no strict 'refs'; no warnings 'redefine'; { local *a = sub { "changed" }; print_a(); # prints "changed" }
The extent of the change is limited to the dynamic scope of the block in which the local-ized assignment is made. If I call print_a from outside of the block, its call to a will invoke the original "unchanged" definition:
print_a(); # prints "unchanged"
Let's say I want to do this kind of temporary overriding frequently. I can create a helper subroutine to make the process more convenient:
sub localize_a_and_call_fn(&@) { my ($fn, @args) = @_; local *a = sub { "changed" }; $fn->(@args); }
Now I can run any code I want within the scope where a is temporarily overridden:
localize_a_and_call_fn( \&print_a ); # prints "changed" localize_a_and_call_fn { print "a() => ", a(), "\n"; }; # prints "a() => changed"
That's great.

But let's say I want to take it one step further (which, in fact, I did). Let's say I want to write a more general helper that lets me temporarily override any given list of subroutines – say a, b, and c. My first attempt went like this:

sub localize_and_call_fn { my ($locals, $fn, @args) = @_; local *$_ = sub { "changed" } for @$locals; $fn->(@args); }
That seems simple enough. Unfortunately, the code does not work:
localize_and_call_fn( [qw(a b c)], \&print_a ); # prints "unchanged"
The problem seems to be the for modifier on the simple statement that attempts to localize the given subroutines. Even though perlsyn does not say so, it appears that the simple statement to which the modifier is attached is evaluated within an implicit block, at least as far as local is concerned. It's as if the statement had been written like this:
# for (@$locals) { # local *$_ = sub { "changed" }; # }
None of the local changes can escape the for loop, and thus by the time the helper subroutine invokes $fn->(@args), the original definitions of a, b, and c have been restored. The invoked subroutine will never see the changes.

I could not think of any way to use a simple loop to make local changes for a given list of symbols. By using nested anonymous subroutines, however, I was able to do it. (One could also use explicit recursion.) Here's the code I used:

sub localize_and_call_fn_2 { my ($locals, $fn, @args) = @_; for my $sym (@$locals) { my $f = $fn; $fn = sub { local *$sym = sub { "changed" }; $f->(@_); } } $fn->(@args); } localize_and_call_fn_2( [qw(a b c)], \&print_a ); # prints "changed"
The for loop in the new helper function wraps anonymous subroutines around the seed of code given in $fn. Each of the wrappers overrides a single symbol's definition and then passes control the next wrapper. The last wrapper invokes the original seed of code. In effect, the call to localize_and_call_fn_2 above gets converted into the following code:
# (sub { # local *c = sub { "changed" }; # (sub { # local *b = sub { "changed" }; # (sub { # local *a = sub { "changed" }; # (\&print_a)->(@_); # })->(@_) # })->(@_) # })->();
It seems like a roundabout way to accomplish what ought to be easy, but it works. Can you think of a better way?

In reply to A general method of locally overriding subroutines by tmoertel

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (1)
As of 2024-04-18 23:46 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found