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

This should be easy, but I don't remember seeing in the docs. What's the most elegant way of having methods support being called as both a package method and as a class/object method?

In a method without arguments, it's pretty easy since there will be either no argument, or the first argument (self) will be ignored:

package MyPackage; sub foo() { print 'stuff'; } 1; MyPackage::foo(); MyPackage->foo();

However, when I go to add argument to foo(), I'm not quite sure what the safest way is to see how the method was called.

package MyPackage; sub foo() { my $self = shift; my $text; if (ref($self) eq __PACKAGE__) { $text = shift; } else { $text = $self; }; print $text; }; 1; MyPackage::foo('stuff'); MyPackage->foo('stuff');

Now, checking the ref($self) works, but if I inherit and override this package, self could be many things.

I suspect there is some ->can, ->isa, and ref() magic involved, but I just haven't stumbled on it yet. I think the Digest::MD5 module works this way.

Yes, I know there are probably no good reasons to do this, other than some people like to use OOP, and some don't. I figure why not impliment both styles and let end users decide, and learn something new along the way.

-=Chris

Replies are listed 'Best First'.
Re: Methods supporting both package and OOP style calls
by dragonchild (Archbishop) on Aug 06, 2002 at 03:27 UTC
    I have a stupid question - Why on earth do you want to do this? You're just complicating your code for, what seems to me, minimal benefit.

    That said, if you want to use procedural calls, invest in looking at Exporter. Learn that magic, then assume that if it's a procedural call, there is no $self. If it's an OO call, there is a bless'ed $self. There is no package-call.

    (Package calls are bad, anyways, due to the fact that you're hard-coding stuff. Very NOT-scalable.)

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      Like I said... " Yes, I know there are probably no good reasons to do this, other than some people like to use OOP, and some don't. I figure why not impliment both styles and let end users decide, and learn something new along the way. "

      All that aside, I have run into situations where 90% of the time, I'm using a class as a whole to do related work in that class, while the class keeps it's own private state information. But sometimes, I just want to call one of the classes methods outright to get work done, even multiple times without having to create an object or have that object keep internal state that will never get used.

      Take the DBI modules' quote() for example. Most of the time, I will have a $dbh object sitting around in which to call the $dbh->quote() method.

      But what if I'm just interested in using DBI::quote() directly on some string data without having to create a new $dbh object and all the possible overhead that comes with creating and object class/instance I will never use?

      Sure, I can create 2 seperate methods, that both call a 3rd private method, but than I've got two methods that do the same thing. It would seem better to have a single method do the right thing in either situation.

      Update
      I use Exporter all the time. The problem still comes down to how do you tell that it's a procedural call (thanks, that was the word I was looking for!) or a blessed ref OOP call?

      What do you test the first arg against? ref(shift) could be a multitude of things, some OOP related, some not OOP related, or I'm missing something.

      Update Redux
      File::Spec has a good solution I think. File::Spec used OOP style calls, while you can use the procedural methods in File::Spec::Functions

      -=Chris

Re: Methods supporting both package and OOP style calls
by hossman (Prior) on Aug 06, 2002 at 03:02 UTC
    1. Try runningthe code you have. MyPackage->foo('stuff'); isn't going to do what you think it will
    2. You should take a look at perlboot, perltoot, perltootc, perlobj, and perlbot. In particular grep perlboot for "Making a method work with either classes or instances"
    3. If you want a method to work in all three of the following cases...

      my $foo = new MyPackage(); $foo->foo('test1'); MyPackage->foo('test2'); MyPackage::foo('test3'); # not handled by the case above

      ...it's a little trickier, because in the second case "MyPackage" is just passed as a scalar. I believe the only ways to distinguish between the second and third situations are:

      • Expect something that isn't a 'string', and if the first arg is a 'string' ignore it.
      • Expect a parameter list of a specific length N, and if the list has N+1, ignore the first.

Re: Methods supporting both package and OOP style calls
by frag (Hermit) on Aug 06, 2002 at 04:38 UTC
    The magic you seek is UNIVERSAL::isa.
    if (UNIVERSAL::isa($self, __PACKAGE__)) { print "1st arg was object OR package name: "; $text = shift; } else { print "1st arg is just an arg: "; $text = $self; };

    I concur with dragonchild though: pick some subset of the 3 possible WTDI (function, class method, object method) and stick to it, and make it clear in the docs which is preferred. If people using the module get used to "Foo::bar(arg)" then what if some subs really must have the object passed in? Will it be easy to remember which form goes with which subroutine?

    Personally, I think the sensible way is: public subroutines should be all methods or all functions (in which case it isn't an object package), or else mostly methods with a simple exported procedural function wrapping the most central method (see Data::Dumper). Private subs can vary freely, depending on if they manipulate object/class data or not. (And note this all assumes that speed isn't much of a factor - procedural function calls are always faster than methods.)

    -- Frag.
    "Never could stand that dog."

Re: Methods supporting both package and OOP style calls
by perrin (Chancellor) on Aug 06, 2002 at 14:50 UTC
    Any code that can support both styles of calling is not really OO in any meaningful way.
      That is the correct answer, here is the longer version.
      Sometimes it feels easier to hack a method to work both ways, but it is Bad(tm). You probably aren't using and data in self if you can call it with & without an instance. This means you should be doing
      $answer = Package::foo($ob->member) or $answer = Package::foo('hibill')
      If you are doing using something in $self if it is an object, and doing something slightly different if it is being called as a package function, then it isn't the same function. In this case put the common functionality in foo_common() and have two subs - one for instaces and one for class method - that do the slightly different things. This will make the code readable because people can tell they are different without having to know that soemthing in $self is used invisibly.

      -jackdied

      Please elaborate -- if the code works as OO code, what does it matter if it does something outside of what is normally OO?

      Why is a superset of functionality not implementing the subset in "any meaningful way"?

      I do agree that doing this sort of thing is asking for trouble and not a "clean" way of going about things; your statement struck me as dogmatic, however.

      Matt

      P.S. See Tracking Inheritance Directly due to Hybrid Methods and On Sinning and Subclassing Recalcitrant Modules for some details on my recent adventures with Time::Piece for more information on this very topic. All the trouble sources from having a constructor acting either procedurally or as a method; the problems could be avoided by isolating the procedural aspects:

      sub constructor { my $thing = shift; my $class = ref $thing || $thing; bless {}, $class; } sub factory_proc { __PACKAGE__->constructor(@_); }
        Why is something that can be called as a sub rather than a method not OO in any meaningful way? Subs are not attached to a data structure. Subs don't support inheritance. Subs don't support polymorphism (aka "late binding").

        Perl being what it is, there are cheats to make a sub approximate these behaviors, but they are generally obfuscated and dangerous. If you want an object, use one. If you don't need any of that stuff, don't use OO.

        Modules like CGI.pm give many people incorrect ideas about OO, i.e. that it's nothing more than a different syntax. If you look inside the code for that monster you see that CGI.pm only works at all by using a bunch of globals and assuming that only one CGI object will exist at a time. And ask Ovid how much fun it is to subclass.