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

Hi everyone, I'm trying to override a function, let's call it "lol", inside a module, let's call it "zor", from outside of it.
Doing this on my main file works:
package zor; sub lol { return 'whatever'; }
The problem is this module has a variable, let's call it "varry", declared with "my" which I cannot seem to change, even when using my overrided function.
This does not change the variable:
package zor; sub lol { $varry = 'new'; return 'whatever'; }
How can I change "$varry"? Is it even possible?

Replies are listed 'Best First'.
Re: Changing Local Variable Out of Context
by tobyink (Canon) on Jun 13, 2023 at 12:36 UTC

    If in the original module, $varry is declared outside the sub and accessed within subs, like this:

    package zor; my $varry = "Hello world\n"; sub lol { print $varry; }

    Then you should be able to poke at it using PadWalker. It's a bad idea, sure, but it's feasible.

      In general it's best practice in any programming language to minimize scope, and often, it's helpful to initialize. A var name just *appearing* in a sub, with no local declaration, or not passed in, is at best confusing, and at worst a disaster. It might be defined "miles away" from the sub. The poor bloke maintaining it years from now may have a devil of a time finding it, AND what if it's defined in many places?

      bareblocks help minimize scope, I think about using them whenever I program. Many programmers scope at the "sub" level, but it helps to scope even deeper- within a sub. In general my rules for tite code include:

      a Var's lifetime should be never preceed, or outlast, its usefulness Declare at the last possible moment; go out of scope at the earliest
      Here where I want to get an XML value from a file I might use

      my $XMLval=''; { open X,'myfile'; my @X= grep /<myfavoritetag>/,<X>; last unless @X; $X[0] =~ s/<\/?[^>]+>//g; # shoot the tag and end tag.. $XMLVal=$X[0]; } # here we have $XMLval ready to go , all cleaned up, # and @X is out of scope , # so no worries about it anymore or any other gobbltygook # in the braces

      I could have, and used to, just let @X "hang around" until the sub exited. After 30 years in Perl though, I'm much more conscious of scope and it's hazards.

      It also makes intent clearer- when leaving the bareblock, there is no ambiguity about if @X might be needed, or used, subsequently. Some bloke looking at my code won't have to try to "figure that out" later.

      But worst of all are global vars. To me, using those is like depending on a "side effect". As a min, declare them in main , but where needed, PASS THEM in as a ref or by value.

      Just some musings about scope...
        But worst of all are global vars. To me, using those is like depending on a "side effect". As a min, declare them in main , but where needed, PASS THEM in as a ref or by value.

        Using a global variable as a variable in a procedural manner to store temporary information can be bad, but globals have their place, and especially in perl with the 'local' keyword that allows them to act like environment variables during the scope of a function.

        Also, I'm a fan of end-user freedom, so in all my modules I declare my package variables using 'our' instead of 'my', because it's not for me to judge whether some user of my module should override that or not. That would save the OP from jumping through the hoops of PadWalker just to customize some bit of behavior. (whether a better approach is possible for them is a different argument)

        «But worst of all are global vars…using those is like depending on a "side effect"…where needed, PASS THEM in as a ref…»

        You contradict yourself.

        «The Crux of the Biscuit is the Apostrophe»

        But worst of all are global vars. To me, using those is like depending on a "side effect".

        Global variables have their use cases. In Perl, there are probably more than you think. STDERR for example.

        Global variables can be even more important with other programming languages and use cases. For example in Arduinos and other microcontroller applications. When you only got 1024 RAM bytes to work with, you want to be efficient and not "waste" any on unneeded OO stuff.

        PerlMonks XP is useless? Not anymore: XPD - Do more with your PerlMonks XP
Re: Changing Local Variable Out of Context (updated)
by LanX (Saint) on Jun 13, 2023 at 13:30 UTC
    > Is it even possible?

    Yes, with PadWalker it's possible to mess with such internals. See demo.

    But as Toby already said, it's probably not a good idea especially for production code.

    NB: After calling your patched sub, you'll change $varry for it's whole (file?) scope in that module. This can lead to very unfortunate side effects, if carelessly done.

    use v5.12.0; use warnings; # ========= mock original module BEGIN { package zor; my $varry = "DEFAULT"; sub lol { $varry = 'OLD'; return 'whatever'; } sub get_varry { return $varry } # ========= old behaviour warn get_varry; warn lol(); warn get_varry; } package main; # ========= monkey patch sub { package zor; use PadWalker qw/closed_over/; my ($h_closure,$s_varry); # ===== get scalar ref of varry BEGIN { $h_closure = closed_over(\&lol); $s_varry = $h_closure->{'$varry'}; } # ===== new sub no warnings 'redefine'; sub lol { $$s_varry = 'NEW'; return 'whatever'; } } # ========= new behaviour warn zor::lol(); warn zor::get_varry; # OOPS

    DEFAULT at d:/Perl/pm/change_my.pl line 21. whatever at d:/Perl/pm/change_my.pl line 22. OLD at d:/Perl/pm/change_my.pl line 23. whatever at d:/Perl/pm/change_my.pl line 54. NEW at d:/Perl/pm/change_my.pl line 55.

    UPDATE

    changed code to better demonstrate side effects on $varry

    Cheers Rolf
    (addicted to the 𐍀𐌴𐍂𐌻 Programming Language :)
    Wikisyntax for the Monastery

Re: Changing Local Variable Out of Context
by ikegami (Patriarch) on Jun 13, 2023 at 13:33 UTC

    Sounds like you're trying to access a my variables that's declared in another file. That would be outside of its scope (where it's visible). You can't. The whole point of using lexical variables is to prevent this.

    You need to change the existing module to expose the variable (e.g. by providing an accessor).

    (Otherwise, you will need to grab a reference to the variable from a sub that captured it using PadWalker. This would qualify as a hack.)