Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

Re: how to declare a local *subname=sub{}?

by haukex (Archbishop)
on Oct 31, 2016 at 09:39 UTC ( [id://1175003]=note: print w/replies, xml ) Need Help??


in reply to how to declare a local *subname=sub{}?

Hi perl-diddler,

Although LanX has already explained several things in various posts, one keyword that has not been named in this thread is dynamic scope, personally I find this easiest to remember. local only affects things in the current dynamic scope - as explained in Temporary Values via local(), it changes the value of a global at runtime, which will also affect any functions called at runtime in the scope where local is in effect, but at the end of the scope, the previous value of the variable is restored. An example:

our $foo = "foo"; { local $foo = "bar"; sub quz { print "\$foo = $foo\n"; } } quz(); # prints "$foo = foo"

Once you realize what local is doing, you'll see that the above code is basically equivalent to this:

our $foo = "foo"; { $foo = "bar"; sub quz { print "\$foo = $foo\n"; } $foo = "foo"; } quz(); # prints "$foo = foo"

Note how quz is called after after the original value of $foo is restored. So I hope that explains why your local *asub = sub ... would cease to have any effect once Perl has completed executing your block { package Do::Stuff; ... }.

Your use of *bsub warrants some closer inspection though. In your code example, if you call $p->stuff after $p->do_internal_sub, suddenly the call the bsub works! It's a bit complicated, but if you keep in mind the distinction as to when which line is executed, it makes sense. I'll explain using this example:

#!/usr/bin/env perl use warnings; use strict; sub foo { print "orig foo\n" } sub bar { print "orig bar\n" } { local *foo = sub { print "local foo\n"; }; local *bar; sub quz { foo(); eval { bar(); 1 } or warn "calling bar failed: $@"; } sub baz { *bar = sub { print "local bar\n"; }; } quz(); # first call to quz baz(); # first call to baz quz(); # second call to quz } quz(); # third call to quz baz(); # second call to baz quz(); # fourth call to quz __END__ Subroutine main::foo redefined at local_ex.pl line 8. local foo calling bar failed: Undefined subroutine &main::bar called at local_ex +.pl line 12. local foo local bar orig foo orig bar Subroutine main::bar redefined at local_ex.pl line 15. orig foo local bar
  1. When local *bar; is executed, its value is cleared (Update: this is the intended effect of local, although the documentation is admittedly a little hard to find). Thus, the first call of quz();, while the local *bar; is still in effect and baz has not been executed, will not have a sub bar to execute.
  2. Then, we call baz();, which assigns a value to the currently still localized bar, so the second call of quz(); prints the two "local ..." strings.
  3. Once the block is exited, the effect of local ends and the original definitions of foo and bar are restored, so the third call to quz(); prints the two "orig ..." strings.
  4. Then we call baz(); a second time, which redefines bar. Now what's important to remember here is that the effect of the local *bar; happened at runtime, so its effect is already over! Which means that now, baz globally redefines bar.
  5. The final call to quz(); now still sees the original foo, but bar has been redefined globally.

Having said all that, I think local *foo = sub {} is one of those things that should only be used very rarely (one of those "use only if you know what you're doing and why" things). Aside from the plain wackyness of the above example code, one of the main arguments is that, as always, fiddling with global definitions can cause unintended consequences in other places in the code: if you redefine foo and then call another function, who's to say whether someone down the call tree is depending on the original definition of foo?

The two much better and "more modern" solutions IMO are simply passing around references to (anonymous) subs, or an OO design with classes where methods can be overridden in subclasses; based on what you've written, I suspect the latter might be appropriate in your case, but you'd have to explain a bit more the bigger picture of what you're trying to do.

Update: Yet another reason not to use local *bar; - it localizes everything named bar! Example in readmore tags:

our $bar = "BBB"; sub foo { print "orig foo (bar=$bar)\n" } sub bar { print "orig bar (bar=$bar)\n" } { local *foo = sub { print "local foo (bar=$bar)\n"; }; local *bar; # this also clears $bar! sub quz { foo(); eval { bar(); 1 } or warn "calling bar failed: $@"; } sub baz { *bar = sub { print "local bar (bar=$bar)\n"; }; } quz(); # first call to quz (OOPS, $bar is undef!) baz(); # first call to baz quz(); # second call to quz (OOPS, $bar is still undef!) }

(This behavior does not happen with to the local *foo = sub { ... }; form.)

A few more notes on your code:

  • "sub stuff() {" - prototypes are ignored on method calls.
  • &asub("stuff") - the &foo() style of calling subs disables prototype checking and changes the behavior of @_ (perlsub) - in modern Perl, it has become another one of those "use only if you know what you're doing and why" things.
  • eval {...}; if ($@) ... is discouraged, use eval {...; 1} or ... instead, see e.g. Bug in eval in pre-5.14 or the "Background" section of Try::Tiny.
  • When posting code examples, the easier it is to run for everyone, the better. In this code, your P appears to be doing the same as printf, so if you'd just use printf, people could just download and run your code instead of installing a new module.

Hope this helps,
-- Hauke D

Updated wordings in a few places.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (5)
As of 2024-04-25 10:31 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found