Beefy Boxes and Bandwidth Generously Provided by pair Networks vroom
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Redefining Imported Subs: of scope and no

by temporal (Pilgrim)
on Feb 08, 2013 at 22:59 UTC ( #1017898=perlquestion: print w/ replies, xml ) Need Help??
temporal has asked for the wisdom of the Perl Monks concerning the following question:

Hello there monks,

I feel like this is something that should be picked to pieces somewhere but my best searching efforts haven't turned up an answer for my case. So I'll provide a unexpectedly long example and hopefully some wise monk can nail this down:

I have (as usual) an admittedly clunky script that use feature 'say' for basic debug outputs. What can I say, I was writing this pretty hastily and didn't think this script would evolve to the point where I would need to consider verbosity. All those annoying say messages are certainly unnecessary to the users but I'd like to keep them in the code just for later debugging.

Yeah, hold on, I know you probably have all kinds of simple fixes - let's nix those right now and move on to the actual problem at hand.

I could write a cute one-liner that goes and comments out all the say statements - then I could toggle as needed from CLI. Nope! Unfortunately some of them were unwisely inserted in compound and|or statements or are just otherwise hard to identify and exclusively comment. It doesn't help that my unhealthily compulsive use of PerlTidy leads to multi-line statements based on a line length limit, so no dice.

I could create a verbosity variable and use that to turn each individual say statement on or off:say 'this is a feature' unless $verbose; No, thank you! I hate having to write that into each statement or even doing regex sub to make it happen. That's just entirely too much repetition.

I'm down to a couple options. First option being that I roll my own say sub:

sub say {print shift, "\n" if $verbose}

It's easy, sure. But that's not the point! I want to be able to disable or redefine an imported subroutine. Secondly, I could replace all the says to warn. Not going to happen either, I use warnings already because they are error messages for users of this script not just status updates and boring information dumps. Retrospect tells me that maybe warnings are better suited for debug output and STDOUT print routines for user output since toggling warnings on and off is simple enough but it is what it is.

Okay, so enter no which should presumably be able to unimport a target routine from any module that supports it. Deleting it right out of definition in the current package scope. Think I've got that right.

Unfortunately I can't have say something like no feature 'say' unless $verbose;, can't use or no in conditional or anything like that. Probably something to do with that you can't have code blocks (like BEGIN) get conditionally executed due to Perl's interpreter flow, correct me if I'm wrong. But I guess I can try this:

use feature 'say'; unless ($verbose) { no feature 'say'; #say 'this goes unsayable'; } say 'said';

No dice on that, no only applies to the current code block {}. I was expecting it to operate over the package's scope. So it operates kind of like local does.

Let's try something new; how about manually deleting/redefining the subroutine in the symbol table?

use feature 'say'; undef &say; *say = \&not_say; say 'test'; sub not_say { print 'not saying'; }

Maybe I'm getting my namespaces all mixed up because that doesn't seem to redefine the sub at all.

I'm done fooling around - it's CPAN time. The most likely candidate appears to be Sub::Delete, which promises to delete a subroutine (cleanly, I don't yet understand enough of Perl's mechanics to fully understand what it means by that).

use feature 'say'; use Sub::Delete; delete_sub 'say'; say 'testing';

Doesn't do the trick either. I also went back for the past couple examples and tried specifying the sub as 'main::say' and 'feature::say' but got no love there, either.

So my silly example aside, I'm missing a piece of this puzzle. I thought that subs got imported into the current package's scope, which means that I can remove or redefine them. Where is this sub hiding?

TLDR:
How do I dynamically redefine imported subroutines?

Comment on Redefining Imported Subs: of scope and no
Select or Download Code
Re: Redefining Imported Subs: of scope and no
by LanX (Abbot) on Feb 09, 2013 at 02:22 UTC
    Long read!

    (wont try it again! ;-)

    I don't understand why you're using feature qw(say) if you wanna redefine 'say.

    This makes say() a builtin which has to be overridden like described in perlsub#Overriding Built in Functions.

    Otherwise this works:

    $verbose=0; sub not_say { print 'not saying'; } sub say { print @_,"\n"; } { no warnings; *say = \&not_say if not $verbose; } say 'test';

    or even simpler:

    use strict; use warnings; no feature "say"; sub say { print "nothing"; } say 'test';

    OTOH you will certainly have problems if someone manages to globally switch say on.

    My suggestion is to replace say() with something like 'log()' or 'out()' and keep it simple, don't redefine the sub at all, just put the test condition into the function body.

    Cheers Rolf

Re: Redefining Imported Subs: of scope and no
by LanX (Abbot) on Feb 09, 2013 at 03:16 UTC
    > Unfortunately I can't have say something like no feature say unless $verbose;, can't use or no in conditional or anything like that.

    maybe use feature is special, but normally use if should do that.

    update

    works for me:

    use strict; use warnings; use constant VERBOSE=>0; use if VERBOSE, feature => "say"; sub say { print "nothing"; } say 'test';

    Cheers Rolf

Re: Redefining Imported Subs: of scope and no
by 7stud (Deacon) on Feb 09, 2013 at 04:26 UTC

    I don't understand why you're using feature qw(say) if you wanna redefine 'say.

    I had trouble understanding that too. But as I understand it, the op turned on say() with his use statement, and now the op has say() statements littered throughout the code. Now the op wants to figure out a way to make his say() statements print nothing if a switch is set. Note that his custom say() sub doesn't print anything if $verbose is false.

    As for 'use if', I was looking at that too, and I found two problems with it:

    1. 'use if' doesn't work as advertised for me. Here is the syntax shown in the 'if' pragma:

      use if CONDITION, MODULE => ARGUMENTS

      …but it doesn't work for me:

      use if $/ = "\n", 5.012 => qw( say ); --output:-- Can't locate 5.012.pm in @INC (@INC contains: /Users/7stud/perl5/perlb +rew/perls/perl-5.16.0/lib/site_perl/5.16.0/darwin-2level /Users/7stud +/perl5/perlbrew/perls/perl-5.16.0/lib/site_perl/5.16.0 /Users/7stud/p +erl5/perlbrew/perls/perl-5.16.0/lib/5.16.0/darwin-2level /Users/7stud +/perl5/perlbrew/perls/perl-5.16.0/lib/5.16.0 .) at /Users/7stud/perl5 +/perlbrew/perls/perl-5.16.0/lib/5.16.0/if.pm line 13. BEGIN failed--compilation aborted at 2.pl line 1.

      Yet all my perl programs use 5.012 without error. In addition, the 'use if' can't see a my variable in the code, e.g.

      my $verbose = 1;

      Apparently, 'use if' can only see a global variable that exists at compile time. Using our to declare $verbose doesn't work either:

      our $verbose = 1; use if $verbose, 'strict'; $v = 'hello';

      No error. I see that you got around that problem with use constant.

    2. Turning off say() will cause all the say() statements in the op's code to produce errors.

    It looks like 'use subs' can be made to work (I think you originally posted something about that??):

    use strict; use warnings; #use 5.012; #enables say() use subs qw( say ); my $verbose = 1; sub say { if ($verbose) { print shift, " world\n"; } } say 'hello'; --output:-- hello world

      but it doesn't work for me:

      Not surprising, $/="\n" assignment is always true (and assignment ), 5.12 is not a module (maybe you wants features), and 5.12 doesn't export say (features you want, maybe)

      Apparently, 'use if' can only see a global variable that exists at compile time.

      nothing specific to if, that is the nature of compile-time and our/my, initialization doesn't happen until later

      use constant DEBUG => !!( 0 || $ENV{PERL_DEBUG_MYAPPNAME} );
      use if DEBUG , qw' Carp::Always ';

Re: Redefining Imported Subs: of scope and no
by 7stud (Deacon) on Feb 09, 2013 at 04:50 UTC

    or even simpler:

    use strict; use warnings; no feature "say"; sub say { print "nothing"; } say 'test';

    Edit--Hey, that doesn't work! Where's the ability to flip the real say() back on?
      You're confusing me!

      Why do you always seem to ask me if you reply to someone else?

      Please don't be surprised if I'm not replying.

      And please don't simply change your posts after being proofed wrong in a reply.

      > Where's the ability to flip the real say() back on?

      Just swap no with use!

      feature should be scoped (in this example file scoped), which makes much sense when debugging!

      Otherwise the OP could (like already shown) do "use if" for testing a global constant like main::VERBOSE spreading all files.

      Anyway manipulating say for debugging purpose is such a broken idea that I don't wanna invest more into this discussion... :)

      Cheers Rolf

        And please don't simply change your posts after being proofed wrong in a reply.

        Huh? Where? What? Are you talking about the post where I congratulated you on such a simple solution? I didn't see where you 'proofed' that offering congratulations was wrong?
Re: Redefining Imported Subs: of scope and no
by 7stud (Deacon) on Feb 09, 2013 at 05:50 UTC

    Let's try something new; how about manually deleting/redefining the subroutine in the symbol table?

    use feature 'say'; undef &say; *say = \&not_say; say 'test'; sub not_say { print 'not saying'; }

    Maybe I'm getting my namespaces all mixed up because that doesn't seem to redefine the sub at all.

    The confounding thing is that this works:

    use strict; use warnings; use 5.012; #Rule: sub names are entered into the symbol table. sub abc { print "abc\n"; } sub xyz { print "xyz\n"; } local *abc; #gets rid of 'redefined main::abc' warning' *abc = \&xyz; abc; --output:-- xyz

    But this doesn't work:

    use strict; use warnings; use 5.012; sub xyz { print "xyz\n"; } local *say; *say = \&xyz; say 'hello'; --output:-- hello

    Nor does this:

    use strict; use warnings; use 5.012; use subs qw( say ); #Supposedly overrides a built in my $verbose = 1; sub say { if ($verbose) { print shift, " world\n"; } } say 'hello'; --output:-- hello
Re: Redefining Imported Subs: of scope and no
by aitap (Chaplain) on Feb 09, 2013 at 07:09 UTC
    It's probably too late and it doesn't answer your question, but you could use Log::Log4perl, print your debug information with DEBUG verbosity and change the default verbosity to something lower than that before release.
    Sorry if my advice was wrong.
Re: Redefining Imported Subs: of scope and no
by BrowserUk (Pope) on Feb 09, 2013 at 10:43 UTC

    I don't normally repond to threads I frontpage -- I usually only front page stuff I don't know the answer to -- but as it seems that all you have so far are responses confirming what you already discovered.

    Having just re-read your post, I'm no longer sure that you are asking what I originally thought you were asking. Or rather, I see that you could be asking for one of 3 different things:

    • The easiest way of shutting up your debug statements (using say), in this script, without fully deleting them.

      Perhaps the simplest way would be to tie stdout and make the say method do nothing.

    • Or, you might be asking a more generic question of how to have debug statements that you can turn on & off with a simple boolean.

      In that case, you might consider looking at Smart::Comments.

    • Or, the crux of your question might be: how to override feature enabled built-ins.

      In which case, perhaps Devel::Pragma is a starting point.

    Perhaps if you make it clear whichever of those -- or whatever other -- objective is your primary goal, maybe you'll get more helpful responses.


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Redefining Imported Subs: of scope and no
by 7stud (Deacon) on Feb 09, 2013 at 17:45 UTC

    Not surprising, $/="\n" assignment is always true

    Ack!

    5.12 is not a module (maybe you wants features)

    I didn't know that. From the use docs:

    use VERSION also enables all features available in the requested version as defined by the feature pragma,

    In any case, like the op I'm wondering where the name 'say' is stored? It is not in main's symbol table:

    use strict; use warnings; use 5.012; our $x; use Data::Dumper; say Dumper($main::{x}); #%main:: is the name of the symbol table say Dumper($main::{say}); say Dumper($main::{print}); --output:-- $VAR1 = *::x; #typeglob for 'x', in package ::(shorthand for main::) $VAR1 = undef; $VAR1 = undef;

    So it appears that the name 'say' might be in the same place as the name 'print', i.e. not in the main:: symbol table. But then why doesn't 'use subs' succeed in redefining say()? Then again, I can't redefine print() with 'use subs' either:

    use strict; use warnings; use 5.012; use subs qw( print ); sub print { printf "%s %s\n", shift, 'world'; } print 'hello'; --output:-- hello

    I am able to redefine the chdir() builtin:

    use strict; use warnings; use 5.012; use Data::Dumper; say Dumper($main::{chdir}); --output:-- $VAR1 = undef;
    use strict; use warnings; use 5.012; use subs qw( chdir ); sub chdir { printf "%s %s\n", shift, 'world'; } chdir 'hello'; --output:-- hello world

    It appears that some builtins are more builtin than others.

      ...

      Hit the reply link, the reply link, the link that says reply to reply to the post you want to reply to , that is the link you're supposed to click to have threaded discussion, otherwise it is not theaded discussion but a flat list, flat lists are not effective for following a conversation, that is why the twitts and other "chats" use the @username stuff, so click the reply link and its like this isn't some chat and not flat at all...

      As to your question, CORE is CORE::, many threads about CORE::, ex::override

      perl -e " CORE::say 1 ; "
      perl -e " CORE::say 1 ; say 2 "
      perl -E " CORE::say 1 ; say 2 "

        Say::Compat - Backwards compatibility wrapper for say()
        > Hit the reply link, the reply link, the link that says reply to reply to the post you want to reply to , that is the link you're supposed to click to have threaded discussion, otherwise it is not theaded discussion but a flat list, flat lists are not effective for following a conversation, that is why the twitts and other "chats" use the @username stuff, so click the reply link and its like this isn't some chat and not flat at all...

        Great hip hop ... I'd love to see a XKCD referencing this song! =)

        Cheers Rolf

Re: Redefining Imported Subs: of scope and no
by ikegami (Pope) on Feb 11, 2013 at 06:10 UTC

    How do I dynamically redefine imported subroutines?

    That's actually quite easy, but say is not being imported; it's not even a subroutine! say is an operator, and use feature 'say'; merely controls whether the compiler recognises it or not. (It always recognises CORE::say, though.)

    Some operators can be overridden from within Perl by overriding GLOBAL::CORE::op, and some can't. The following checks whether say can be overridden this way:

    >perl -E"say defined(prototype('CORE::say')) ?'yes':'no'" no

    Its funky syntax doesn't permit it.

    say say LIST say FILEHANDLE LIST say BLOCK LIST

    It could be done using a CallChecker, but that involves dropping to C/XS.

      > ... merely controls whether the compiler recognises it or not. (It always recognises CORE::say, though.)

      Not always, at least for me, with which version did you check?

      lanx@nc10-ubuntu:~$ perl -e 'CORE::say("bla")' CORE::say is not a keyword at -e line 1. lanx@nc10-ubuntu:~$ perl -E 'CORE::say("bla")' bla lanx@nc10-ubuntu:~$ perl -version This is perl, v5.10.0 built for i486-linux-gnu-thread-multi

      > say is an operator, and use feature say; merely ...

      As a minor nitpick, according to the definitions in perlglossary I'd rather use built-in and not operator to describe say, though the differences are indeed fuzzy.

      Cheers Rolf

        Not always, at least for me, with which version did you check?

        It apparently went missing until 5.16.

        As a minor nitpick, according to the definitions in perlglossary I'd rather use built-in and not operator to describe say, though the differences are indeed fuzzy.

        Builtin functions are a slight superset of what both perlop and perlfunc call list operators and named unary operators. say is a builtin because say is a list operator. (It's fuzzy which builtins aren't operators.)

Re: Redefining Imported Subs: of scope and no
by Dallaylaen (Scribe) on Feb 11, 2013 at 13:46 UTC

    Don't mess with say, it will byte you or a later maintainer of this code.

    Instead, (1) define sub debug {...}; with a plain old if inside. (2) Replace all occurences of say with debug(). (3) Replace those say's that aren't really for debugging back again. (4) Re-run your tests to see if anything got lost.

    You can then consider moving debug() to a separate module for re-use, or switch to Log4perl altogether as other monk suggests here, or even write a debug_off() sub that replaces debug() on the fly. But at least it's not like "Hey, say() prints nothing! Our perl interpreter must be broken!"

Re: Redefining Imported Subs: of scope and no
by temporal (Pilgrim) on Feb 11, 2013 at 16:40 UTC

    Whoa, had no idea that this would spark such a lively discussion. Excellent.

    I had intended to use say as an example - but it turned out to be the problem, huh? Ha! Go figure.

    LanX - did not know about use if, so that's a neat plus. Hopefully you haven't quite yet learned your lesson about reading and replying to my ridiculously long posts ♥

    Also comforting is that most of my solutions will work with normal non-CORE:: namespace imports.

    As for all the advice in regards to how I ran into this question - debug output style/best practices and such - in the future I'll be going with aitap's great suggestion: Log::Log4perl. Very flexible and feature-rich, thanks for the tip!

    BrowserUk, after diving the rabbit hole of replies, my question is/was/will be where do these definitions reside and how do I get at them?
    Not so much how to get around a silly design decision I made =D
    As usual, thanks for pointing me towards some great reading material. I'll definitely be playing around with Devel::Pragma to investigate any future questions in this vein.

    So here we go:

    • say lives in CORE::, where it can (usually) be accessed
    • See ex::override for overriding CORE:: functions.
    • use feature 'say' enables say as an operator - which means that once created it cannot be redefined.
    • use if works, but has some constraints concerning order of your sub and verbosity definitions.
    • Using say for debugging is silly - but educational!

    Thanks to all of you for the great replies and entertaining repartee ;)

    Strange things are afoot at the Circle-K.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1017898]
Approved by davido
Front-paged by BrowserUk
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (7)
As of 2014-04-17 04:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (439 votes), past polls