tags:

views:

175

answers:

3

I'm writing a function that is mostly static in nature. I want to plug it into Template Toolkit, which passes along the class name. In essential, it is doing

ClassName->function( $args.. )

but I want it to do something like

ClassName::function( $args.. )

inside

sub function {
}

what is the proper way to handle both cases?

+7  A: 

In general, there isn't. a sub is either written to be called as a method or it isn't.

See how File::Spec::Functions handles this situation by prepending the package name to the argument list.

Now, in a very specific, limited case, you can do:

shift if $_[0] eq __PACKAGE__;

as the first line in your sub to discard the first argument when the sub is called as a class method.

Sinan Ünür
+1. This becomes safer if each of your functions takes a fixed number of arguments, in which case you can decide whether OO call syntax was used based on whether there is 1 more parameter than expected. (As it stands, calling "ClassName::function('ClassName');" will trigger a false positive.)
j_random_hacker
@j_random_hacker Thank you.
Sinan Ünür
+4  A: 

Template Toolkit expects it's plugins to use OO, so there's no way around providing that interface. If you also want a functional interface you have a couple of options.

Perl doesn't really distinguish between a function and a method. The main difference is that the method invocation syntax implicitly includes the object reference (or class name, depending on how it was invoked) as the first argument. You can use function call syntax and provide the referent manually:

ClassName::function('ClassName', @args);

but that's messy. The cleaner solution would be to split it into two subs with one a wrapper for the other. e.g.

package ClassName;

sub function {
    # do something
}

sub method {
    my $class = shift;
    function(@_);
}

The function could be a wrapper around the method as well. As Sinan alluded to, File::Spec does this by creating two modules: one with the OO interface and one with a functional interface.

Michael Carman
Or let Perl do the wrapping for you: "package ClassName::Functional; sub AUTOLOAD { no strict refs; ClassName->$AUTOLOAD(@_); }"
j_random_hacker
@j_random_hacker: no strict refs should be no strict "refs" or it fails under strict subs :) except that it isn't needed because ->$method is allowed under strict refs. And $AUTOLOAD is fully qualified, so you need our $AUTOLOAD=~s/.*:://; or you get infinite recursion.
ysth
@ysth: Thanks! (So many bugs in so little code, wow...) Any idea why "->$method" is allowed without strict 'refs'? Just another arbitrary Perl inconsistency?
j_random_hacker
ysth
+5  A: 

Here is a safer version that combines Sinan's and Alan's answers, and also:

  • Handles the possibility that the method is called from a derived object
  • Won't misinterpret ClassName::function("ClassName") as a method call

The code:

if (@_ == $nArgsExpectedForThisFunc + 1) {
    $_[0] eq __PACKAGE__ || UNIVERSAL::isa($_[0], __PACKAGE__) || die;
    shift;
}

This requires that you know the number of arguments to expect; if you don't, but your first argument can be differentiated from the possible allowable values that might be sent when it is called as a method (e.g. if your first argument must be an arrayref), the same general principle can still be applied.

j_random_hacker
+1 Nice combo but I also like Michael Carman's wrapper idea: http://stackoverflow.com/questions/1038975/perl-function-with-or-without-class/1039085#1039085
Sinan Ünür