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

Hello fellow monks! I ran into the following problem and came in search of your advice...

I'm trying to create a 'safe sub' that when called using any of the methods a sub can be called with will still work the same.

So --

my $obj = new PKG; $obj->safe_sub({'foo' => 'bar'}, 'cool'); PKG->safe_sub({'foo' => 'bar'}, 'cool'); PKG::safe_sub({'foo' => 'bar'}, 'cool'); package PKG; &safe_sub({'foo' => 'bar'}, 'cool');
And here's what I've got so far:
package PKG; sub safe_sub { # The next line contains the purpose of my post... shift if ref $_[0] or $_[0] =~ /^PKG/; # The below is just to make sure it's working (or not working :) my ($hash, $string) = @_; print "$hash->{foo}\n$string"; } sub new { bless {}, shift; }

Replies are listed 'Best First'.
Re: Calling Madness
by broquaint (Abbot) on Sep 15, 2002 at 00:43 UTC
    You could try
    { package PKG; sub safe_sub { my $self = shift if @_ == 3 and UNIVERSAL::isa($_[0], __PACKAGE__) or $_[0] eq __PACKAGE__; my($hash, $string) = @_; print $hash->{foo}, ':', $string, $/; } sub new { bless {}, shift } } my $obj = PKG->new(); print '$obj->safe_sub - '; $obj->safe_sub({'foo' => 'bar'}, 'cool'); print 'PKG->safe_sub - '; PKG->safe_sub({'foo' => 'bar'}, 'cool'); print 'PKG::safe_sub - '; PKG::safe_sub({'foo' => 'bar'}, 'cool'); package PKG; print 'safe_sub - '; safe_sub({'foo' => 'bar'}, 'cool'); __output__ $obj->safe_sub - bar:cool PKG->safe_sub - bar:cool PKG::safe_sub - bar:cool safe_sub - bar:cool
    Which seems to work. If you're looking for methods which act like functions and vice versa check out the source for CGI.pm as it performs a similar kind of magic.
    HTH

    _________
    broquaint

Re: Calling Madness
by chromatic (Archbishop) on Sep 15, 2002 at 02:36 UTC

    It is madness. Don't do it. Instead, write clear documentation that tells how you expect people to use your module, and let them take the hint.

    If you don't take my advice, at least realize that you've effectively broken subclassing with your regular expression. (You're also going to have a dickens of a time overriding safe_sub().)

Re: Calling Madness
by jkahn (Friar) on Sep 15, 2002 at 00:35 UTC
    I am not entirely sure what you're asking, but I'll try to paraphrase:

    Is there a way I can construct a package subroutine so that, regardless of whether:

    • I call it as an instance method
    • I call it as a packageclass method
    • I call it by its fully-qualified Pack::method name
    it will do the Same Thing each time?

    My answer: I think so. I'm not sure why you would want to do this, but it's the only thing I can see here.

    Here's a method that would act that way (although it's not quite what you suggested). (untested):

    package thisPack; sub smartish_sub { my $self; # for instance reference, if called that way my $class; # for classname, if called that way if (ref $_[0]) { if (UNIVERSAL::isa($self,'thisPack')) { # ( corrected from earlier ->isa call) # it's an instance method invocation my $self = shift @_; } # else it's not an instance, but it is a ref, so # it must be a fully-qualified invocation } else { # not a reference; ought to be class invocation my $class = shift; die "first argument to smartsub must be hashref\n" unless UNIVERSAL::isa($class, 'thisPack'); } # now any class- or instance- invocations have been # shifted off @_ my ($hash, $string) = @_; # might want error-checking here. print "$hash->{foo}\n$string"; } sub new { bless {}, shift; }

    YMMV.

    Updated (almost immediately): used UNIVERSAL::isa() everywhere, not ->isa().

    second update: clarified package vs. class language.

      You're on the right track :)

      See - this sub can be called by any method so I need to make sure everytime it's called I either shift off the package/class info or do nothing.

      My orgional code to do this (shift if ref $_[0] or $_[0] =~ /^PKG/;) doesn't work when your calling the sub by its fully-qualified "Package::method" name and $_[0] happens to be a hash ref.

      So maybe this will help in understanding my problem:

      shift if (ref $_[0] and $_[0]-is-the-ojbect-itself,-not-a-normal-hash-ref) or $_[0] =~ /^PKG/;

Re: Calling Madness
by dws (Chancellor) on Sep 15, 2002 at 00:47 UTC
    I'm trying to create a 'safe sub' that when called using any of the methods a sub can be called with will still work the same.

    Time spent studying CGI.pm will pay off in many ways, including for this particular problem.

      I did look at CGI.pm but don't totally understand the UNIVERSAL::isa stuff. And I *think* it's done a little differently then what I need in CGI.pm. (It creates a new ojbect if it's not there whilst I just want to get rid of the object if it is there.)
Re: Calling Madness
by sauoq (Abbot) on Sep 15, 2002 at 08:35 UTC

    Right off the bat: I agree with chromatic. Don't do it.

    Now that I've said that, if you aren't doing anything with the object or package (in the cases where it is invoked as an object or class method) and the rest of your argument list isn't variable, then just pull your arguments from the back end of the array.

    my ($string, $hash) = (pop, pop); # Reverse order... UGLY. my ($hash, $string) = @_[-2,-1]; # Keep "normal" order. (Better) my ($w, $x, $y, $z) = @_[-4..-1]; # Range operator makes long lists ea +sy.

    This avoids all the unneeded argument checking on throwaway arguments.

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Calling Madness
by rir (Vicar) on Sep 15, 2002 at 03:18 UTC
    No, not quite.

    You have to accept:

    PKG::safe_sub( "PKG", $other_args);
    as the same as:
    PKG::safe_sub( $other_args); safe_sub PKG $other_args;

    Also I think you want

    shift if ref $_[0] eq "PKG" or $_[0] eq "PKG";
    as your test.
      ref $_[0] eq "PKG"
      That will break inheritance, though. You really need UNIVERSAL::isa there instead, like in broquaint's post.

      Makeshifts last the longest.

        As Aristotle says, ref $_[0] eq "PKG" will break inheritance.

        I think that illustrates a serious problem with the concept. I took the stance that:
        Class::derived( $not_blessed); should do the same as:
        $class->derived( $not_blessed); and decided that inheritance was not allowed because the first form was desired.

        If allowing naive users to use the various calling conventions
        was the idea, and why else, I thought this was the clear choice.

        The following, and more, need to be considered:

        Class::derived( $class_obj); # Jack Class::derived( Class $not_blessed); # Queen Class->derived( $not_blessed); # King $class_obj->derived( $not_blessed); # Ace Class::derived( "Class", $not_blessed); # Joker

        The last is the joker. How will you distinguish between the Joker and the Queen?
        And what about Jack? Is that the same as:  $class_obj->derived;
        or the same as:  $class_obj->derived( $class_obj);

Re: Calling Madness
by Anonymous Monk on Sep 19, 2002 at 00:22 UTC
    Wouldn't this work: shift if UNIVERSAL::isa($_[0], __PACKAGE__) or $_[0] eq __PACKAGE__;