http://qs1969.pair.com?node_id=37758

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

I know you can do this with closures, but I'm wondering if there is a better way. Here is the example:
a(); b(); print "$var\n"; sub a { local $var = "a\n"; c(); } sub b { local $var = "b\n"; c(); } sub c { # i'm pleasing you to print a or b print "var: $var"; }
So you can see, I want c() to have access to the calling sub's my variables. I know that if I eval the creation of that sub in a() or b() it will work, however I don't want to have to do that for every sub that calls c().

Is there a hack to do the above?

If not, here is my next best choice... the sub c() is actually spit out by a class and evaled in the callers package, is it possible to have the eval happen in the callers namespace?

That is:

$b = new SubMaker(); sub a { my $var; $b->make_sub("subname"); subname(); }
where SubMaker::make_sub() evals in the callers namespace so that subname() now has access to $var. Is that possible?

thanks.

Replies are listed 'Best First'.
Re: access to my variables from other subs
by Adam (Vicar) on Oct 21, 2000 at 04:09 UTC
    You could use global variables (trully global with use vars) but I would use a closure... that's what they're for. By the way it doesn't look like you are using stict. bad idea.
    use strict; # ALWAYS! $a = LetterPrinter( "a" ); $b = LetterPrinter( "b" ); $a->(); # these can also be called with ampersand $b->(); # ie: &$b; sub LetterPrinter { my $letter = shift; return sub{ print $letter, $/ }; }
Re: access to my variables from other subs
by joe (Acolyte) on Oct 21, 2000 at 05:41 UTC
    Thanks Adam,

    Yes, local would probably be the trick, but I don't have that luxury. This thing is supposed to plug into a lot of existing code that uses "my" variables. It will work if I eval the manufactured sub each time, I was just trying to find a way around that because it is such a performance hit. I'm becoming convinced that what I want to do is impossible, and for all I know, it's impossible for a good reason :-)

    From what I've been reading, the "my" variables live on what is known as a "scratchpad", what I was looking for was a way for the manufactured sub to get access to that scratchpad. Of course the scratchpad changes each time the parent sub is called, so the pointer to it in the manufactured sub would have to change with each invocation. I wonder if the rfc period for perl 6 is closed :-)

    Hopefully merlyn will chime in here and tell me why it's such a bad idea to try and do what I am trying to do, because there is probably a good reason not to allow it, I just don't know what that is yet. :-)

      I am not merlyn, but will I do? :-)

      What you are trying to do has serious conceptual problems for reasons I tried to explain in RE (3): BrainPain-Help.

      However instead of saying what it is you want to do, why don't you explain what it is that you are trying to do? Then instead of trying to fight the design of the language we might be able to tell you how to get it done working with the language.

      For instance you could always pass the variables you want access to in as arguments to the function. Or if it is more convenient, pass in references to the variables instead. (Not strictly needed, but it tends to warn the caller that you may intend to do stuff to the variable.)

      I am trying to figure out what you need direct access to your caller's lexical variables for or why providing that wouldn't ruin the entire point of having them - and I am failing...

        Hey Tilly, you're right up there in god status in my mind, so, sure you'll do :-)

        So, what am I trying to do? I am trying to provide an easy to use output functionality to perl programmers, ala Template::Toolkit, embperl, etc... I do understand how to do it by passing variables or eval-ing the sub each time. Both have drawbacks in my mind. The drawback to the passing the variables is that is no longer quite as easy as envisioned: If you decide to display a new variable, you can't just change your template and be done, you now must change the code that passes in the hash or hashref to the display function. Eval-ing each time works ok, but it is molasses slow.

        As for ruining the point of having lexicals, I hope I'm not quite proposing that... The lexicals would only be accessible to the sub that made them, and to subs that were created by the sub that made them. I'd say they'd still be pretty safe from falling into the soup.

        Now I don't know anything about perl internals except from what I read in Advanced Perl Programming but it tells me that lexicals live on a scratchpad for each sub. I think it would be useful if subs created in a sub had access to it's parent's scratchpad, and if the parent that created it is no more, then the last instance of the parent sub that was created. I'd even accede that the created sub must be called from within the parent sub -- but with access to the scratchpad of the current instance of the parent sub, not the scratchpad of the instance of the parent sub that did the actual creation (which may be long gone...).

        I know this is confusing, and I'm probably not explaining myself well, but if you get what I'm saying... what do you think of it?

        thanks

Re: access to my variables from other subs
by joe (Acolyte) on Oct 21, 2000 at 04:56 UTC
    Okay Adam... use strict is in place. Here is another example of the problem:
    use strict; my $evaled = 0; # very simplified... sub makesub { my($name) = @_; return "sub $name { print \"\$var \"; }"; } sub a { my($var) = @_; if(!$evaled) { eval(makesub("somesub")); $evaled=1; } } a("what"); somesub(); a("the"); somesub();
    prints:
    what what
    
    I'd like it to print "what the". Basically I don't want to have to eval every single time, it could be in the millions... but I'd still like access to the my variables of the sub that it was first created in. I don't even mind if I have to call the sub from the one that encloses it. i.e:
    sub a { my($var) = @_; if(!$evaled) { eval(makesub("somesub")); $evaled=1; } somesub(); # only place I call it from. # only want to eval once per # program execution, but have # access to the my variables for # each calling of "a" }
    Perhaps it's impossible.
      #!perl -w use strict; # ALWAYS! a("what"); somesub(); a("the"); somesub(); { my $var; # shared only between a() and somesub() sub a { $var = shift } sub somesub { print $var, $/ } } # proof that $var is scoped correctly: my $var = 'global'; print $var, $/; somesub(); print $var, $/;
      I should point out that ALL named subroutines are global to the package in which they are named. Thus declaring a named subroutine inside another routine does not do what you want. Anonymous subroutines, on the other hand, are scoped locally (for relatively obvious reasons). Of course, the routine can only see variables that are in the scope where the routine was declared...

      Update: I'm looking at this, and it doesn't look like your original question. It seems you wanted named routines that came from the same code but did different stuff based on some third party manipulator function... let me think on that.

        Okay, here you go:
        #!perl -w use strict; # ALWAYS! a("what"); b("the"); { use vars ('$var'); local $var; # shared only between a() and somesub() sub a { local $var = shift; _somesub() } sub b { local $var = shift; _somesub() } # sub routines with a leading _ usually denotes a private func. sub _somesub { print $var, $/ } }
Re: access to my variables from other subs
by Fastolfe (Vicar) on Oct 24, 2000 at 18:57 UTC
    Don't mean to point out the obvious, but just in case you aren't against re-coding slightly or have different reasons for trying to do this that the others might not notice, I want to at least point out the fact that this might be better done via arguments to c():
    &a; &b; sub a { my $var = 1; &c($var); } sub b { my $var = 2; &c($var); } sub c { my $var = shift; print "var=$var\n"; }
    Generally you either want to scope something globally (or at least within the block that these functions will operate), or pass things as arguments and return values between your functions. Sorry if you're already aware of this and have other needs for using local/my variables in a pseudo-global capacity. Just wanted to get this out there.