Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change

comment on

( #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

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

  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?

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

    How do I use this? | Other CB clients
    Other Users?
    Others rifling through the Monastery: (1)
    As of 2020-10-25 03:01 GMT
    Find Nodes?
      Voting Booth?
      My favourite web site is:

      Results (248 votes). Check out past polls.