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

I'm writing a subroutine to serve as both a package-function and as a method. My specific application is to use it for looking up records in a database. The method call would be used if I already have an object that needs to look itself up. The function call would be used to query the database, given a hash of values. The hash would be updated with the DB record, and could be used later in an object constructor.

Any feedback or other comments?

The code below works properly.

#!/usr/bin/perl -w use strict; package xpkg; sub new { my $pkg = shift; # What class are we constructing? my %info = @_; # Allocate new memory bless(\%info, $classname); # Mark it of the right type return \%info; # And give it back } sub mproc { my ($ps) = shift; # either pkg or self my ($pkg,$info); # $info is a ref to hash if (ref $ps eq '') { # it is a function call $pkg = $ps; $info = shift; } else { # it is a method call $pkg = ref $ps; $info = $ps; } $info->{x} = 'y'; print $pkg; } my $x1 = xpkg->new(a => 1); $x1->mproc(); # call as method xpkg->mproc({b => 2}); # call as function

Replies are listed 'Best First'.
Re: feedback on subroutine as both function and method in one
by davorg (Chancellor) on Jul 12, 2001 at 18:07 UTC

    Well, that's not giving the user a choice between a function and a method - it's the difference between an object method and a class method.

    In order to call it as a function, it needs to work as xpkg::mproc or (if exported) just mproc.

    CGI.pm carries this off, but I can't think of any other modules that do it. You might look at the CGI.pm code to see how it's done there - but I think it's all pretty hairy in there :)

    --
    <http://www.dave.org.uk>

    Perl Training in the UK <http://www.iterative-software.com>

Re: feedback on subroutine as both function and method in one
by jeroenes (Priest) on Jul 12, 2001 at 18:53 UTC
    I always like input magic, but there are caveats: What if the user decides to access your sub as a real function? That is, xpkg::mproc()... and worse, if the user additionally throws an hashref at the beginning? Your code wouldn't recognize it...

    Check the following: is it a ref? If not, is it a string with your package as a name? -&bt; you are called as a class. Skip the first argument. If not, keep the first. If it is a ref, is the ref equal to the name of your package? Than the first argument is $self. If not, you need to keep the first argument. You see, four possible cases here.

    Just a nitbit: no memory allocation is needed in perl.

Re: feedback on subroutine as both function and method in one
by bikeNomad (Priest) on Jul 12, 2001 at 20:45 UTC
    My personal bias is against having dual-mode routines (even dual-mode constructors, which seem to be common). I find that with time, the usage of the different modes diverges enough to make them different routines. And it can be semantically confusing. If you want to share code, factor out the common code into a private routine and call it from both your function and your method.

    That said, you can do something like this to detect being called as a function, as a package method, or as an object method, even in the face of inheritance:

    sub myEverythingMethod { my $classOrObject = shift if UNIVERSAL::isa( $_[0], __PACKAGE__ ); if (!defined($classOrObject) ) { # function } elsif (ref($classOrObject)) { # object method } else { #package method } }

    update: fixed setting of classOrObject

Re: feedback on subroutine as both function and method in one
by andreychek (Parson) on Jul 12, 2001 at 21:28 UTC
    Just for fun, this is how CGI goes about doing it:
    sub param { my($self,@p) = self_or_default(@_); blah blah... } sub self_or_default { return @_ if defined($_[0]) && (!ref($_[0])) &&($_[0] eq 'CGI'); unless (defined($_[0]) && (ref($_[0]) eq 'CGI' || UNIVERSAL::isa($_[0],'CGI')) # sli +ghtly optimized for common case ) { $Q = $CGI::DefaultClass->new unless defined($Q); unshift(@_,$Q); } return wantarray ? @_ : $Q; }
    The very first thing done when a sub is called is to test the parameters passed to it.. to see if it's being called as a function or method. Now, if I'm reading this correctly, it would seem that if it's not being called as an object method, then it is turned into an object. Otherwise, it leaves it alone and returns the parameters as is (leaving it as an object).

    So, perhaps I can go as far as saying that everything inside CGI is treated as an object, no matter how you call it.
    -Eric
      It's interesting that it always does the unshift, even if it's not called in array context. I could see that being a problem when subclassing CGI:

      my $cgi = $self_or_default(@_); $cgi->param(@_); # oops, now I have an extra CGI as first arg.