Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

use deprecated;

by halley (Prior)
on Nov 26, 2003 at 14:40 UTC ( [id://310262]=perlquestion: print w/replies, xml ) Need Help??

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

I'm thinking of putting together a basic developer aid for my workplace, a pragmatic module called deprecated. The idea is to detect when a user is probably a developer, and caution the user that they're depending on a deprecated module or symbol.

(In our facility, we have many environment variables which could hint to a script that it's being executed by a developer, and not by a typical end-user. If you have other more general ideas to detect this situation, that's a side question.)

For a wholly-deprecated module, I would just add one line to the top of the module. Then when the top-level script is executed in a developer environment, deprecated::import() would simply carp about how OldeCruft is deprecated. The deprecation is just a warning; no changes are made to the rest of the behavior.

package OldeCruft; use deprecated; # carps if module loaded ...

However, I'd also like to be able to deprecate specific symbols within the crufty namespace, without editing anything else deep in OldeCruft.pm. For example, if OldeCruft::dangerous() is deprecated, then the script should carp about that fact on the FIRST call to that sub.

package OldeCruft; use deprecated qw(dangerous); # carps if OldeCruft::dangerous() is called ...

Is there a straightforward and robust way to hook each named deprecated sub with a separate check-and-carp behavior? I'm not too familiar with the way sub names are joined to a symbol hash, and want to know if manipulating that symbol hash is a risky or generally safe proposition.

Update: use deprecated; contains the tested and documented implementation, from Aristotle's suggestions.

--
[ e d @ h a l l e y . c c ]

Replies are listed 'Best First'.
Re: use deprecated;
by broquaint (Abbot) on Nov 26, 2003 at 15:36 UTC
    Here's one approach
    package deprecated; use Carp qw/ carp croak /; use vars '%subs'; sub import { my($good_self, @subs) = @_; my $pkg = caller; *{"$pkg\::AUTOLOAD"} = sub { croak "unknown subroutine '$AUTOLOAD'" unless exists $deprecated::subs{$AUTOLOAD}; carp "deprecated subroutine '$AUTOLOAD'"; *$AUTOLOAD = $deprecated::subs{$AUTOLOAD}; goto &$AUTOLOAD; } unless defined &{"$pkg\::AUTOLOAD"}; for(@subs) { $subs{"$pkg\::$_"} = \&{"$pkg\::$_"}; *{"$pkg\::$_"} = *{"dummy_$pkg\_$_"}; } } q[ the end is nigh ... ];
    And some example usage
    { package foo; sub func1 { print "old\n" } sub func2 { print "older\n" } sub func3 { print "new\n" } use deprecated qw/ func1 func2 /; } print "calling: func1\n"; foo->func1; print "calling: func2\n"; foo->func2; print "calling: func3\n"; foo->func3; print "calling: func1 again ...\n"; foo->func1; __output__ calling: func1 deprecated subroutine 'foo::func1' at deprecated_test.pl line 13 old calling: func2 deprecated subroutine 'foo::func2' at deprecated_test.pl line 15 older calling: func3 new calling: func1 again ... old
    Note that use deprecated goes after the subroutine declarations, so they're populated when it goes to insert the dummy routines.

    Briefly, what it does is create an AUTOLOAD method that will carp and then replace the dummy subroutine with the actual subroutine.

    HTH

    _________
    broquaint

Re: use deprecated;
by liz (Monsignor) on Nov 26, 2003 at 14:53 UTC
    Nice idea.

    I see one problem with it: you need to change the source of each of the OldeCruft modules. Or install a code reference in @INC that does its magic as a source filter on all modules loaded.

    A solution in which you would just add -Mdeprecated to the command line, would be nicer, I think.

    One way to do this, would be to put all the knowledge about OldeCruft modules in the "deprecated" module and have that check at INIT {} or CHECK {} time. A concept:

    package deprecated; INIT { die "Cannot use OldeCruft\n" if defined $OldeCruft::VERSION; # possibly install wrappers also here }
    This however has the disadvantage that this won't work under mod_perl (as INIT and CHECK dont't work with mod_perl).

    Hope this helps.

    Liz

      Two comments.

      As the company librarian, I can edit the official copy of OldeCruft.pm, but I don't know how many thousands of old private scripts may be depending on it. So I wanted to be able to add one line to OldeCruft.pm and thus all other scripts would start carping automatically. I can't just email the developers to say "add this weird -Mdeprecated thing..."

      I want deprecated to be agnostic; it doesn't care who uses it, it doesn't know which modules are deprecated. By including this pragma, you mark the module as deprecated, and that's all you need to know. Modules come and modules go, and I'm not interested in editing some custom and touchy intelligence in the 'deprecated' module every month.

      --
      [ e d @ h a l l e y . c c ]

Re: use deprecated;
by danger (Priest) on Nov 26, 2003 at 14:58 UTC
    Is there a straightforward and robust way to hook each named deprecated sub with a separate check-and-carp behavior?

    Hook::LexWrap (and others in the Hook:: namespace) will allow you to write your import() routine to wrap deprecated subs to emit the necessary warnings.

Re: use deprecated; (Eiffel's obsolete)
by grinder (Bishop) on Nov 26, 2003 at 15:21 UTC

    Interesting idea. Eiffel had it ten years ago (of course, no-one uses Eiffel, ha ha ha!).

    The idea was to add the idea of deprecatedness into the language. Meyer achieved this by adding the obsolete keyword to the language. A method or class could be labelled obsolete, which meant that whenever new code was written that used it, the compiler would spit out a warning (but would not in any other way affect the semantics, you could continue to use it and it would work correctly).

    Have suffered C macros that wrapped function declarations with warning pragmas at the time to emulate the concept poorly, I found the idea appealing. I still do, I guess.

    As for manipulating the symbol table, it is not particularly difficult to intercept calls to subs and wrap them up in your own stuff. I must say it's one of the more exhilarating experiences one can do in Perl. First time I did it, I kept thinking, "ha! try doing that in C."

    update: minor wordos tweaked.

      A method or class could be labelled obsolete, which meant that whenever new code was written that used it, the compiler would spit out a warning (but would not in any other way affect the semantics, you could continue to use it and it work correctly).

      Hmmm... a universal CODE attribute handler, should allow you to create an "obsolete" attribute that would just do that in Perl.

      Liz

      Yeah, I've long thought Java should have such a keyword; they have every other possible method flag you could possibly want.

      --
      [ e d @ h a l l e y . c c ]

        Java does have a mechanism for depracating code. You can add a marker in the JavaDoc, and the compiler will warn you whenever it encounters a use of such a method. It's kind of hack-rific that they made the compiler parse the comments to do something like this, but it's there.

Re: use deprecated;
by Aristotle (Chancellor) on Nov 26, 2003 at 17:53 UTC
    That was nice food for thought.
    package deprecated; use constant EVAL_CODE => <<'END_CODE'; sub %1$s::INIT { my $overridden = \&%2$s; *%2$s = sub { require Carp; Carp::carp('%2$s() is deprecated'); *%2$s = $overridden; goto &$overridden; }; } END_CODE sub import { my $class = shift; my $pkg = caller; eval join '', map sprintf(EVAL_CODE, $pkg, "$pkg\::$_"), @_; } 1;
    It should be obvious what is happening here, if not just change the eval to a print.

    Makeshifts last the longest.

      Sneaky. Like it.

      You'd also need to check that the subroutine actually exists at INIT time, and wrap AUTOLOAD with something appropriate if it's defined.

      As liz points out this won't work in mod_perl, eval and other places where the INIT phase has already occured.

      I guess you could use a source filter to inline the wrapping code - but that would be really evil ;-)

        Actually, you don't even need to check whether the function exists. If you take a reference to a named but undefined function, it magically works once that function is defined. Perl is devious that way.
        #!/usr/bin/perl $_ = \&x; *x = sub { print "Oh my\n" }; $_->();
        This actually prints "Oh my".

        Makeshifts last the longest.

Re: use deprecated;
by halley (Prior) on Nov 26, 2003 at 14:47 UTC

    A second worry just occurred to me-- the symbol table of subs in the caller (e.g., OldeCruft) is probably not even populated by the time Perl is executing deprecated::import().

    --
    [ e d @ h a l l e y . c c ]

      Memoize may be a good example for you to look at (though you are right, the subs won't be there in time, so you will have to install an INIT {} block, which only works if the use OldeCruft happens at compile-time).
Re: use deprecated;
by linux454 (Pilgrim) on Nov 28, 2003 at 15:23 UTC
    Another thing to look at (for marking methods as deprecated) is Attributes. I myself have never used them, but I think attributes might be an elegant solution to this problem. See Attribute::Handlers and perlsub.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (None)
    As of 2024-04-25 00:35 GMT
    Sections?
    Information?
    Find Nodes?
    Leftovers?
      Voting Booth?

      No recent polls found