rf_circ
rf_circ

Reputation: 1915

Perl: When called in a method, can ref($self) ever return anything other than __PACKAGE__ or undef?

I have method in a class that I need to make sure is only called on an object instance, and not as a class method.

I will probably do something like this:

# Edit: this is terrible, don't do this, it breaks inheritance.
sub foo
{
  my ($self) = @_;

  if (ref($self) ne __PACKAGE__) { return; }

  ...do stuff
}

But I'm thinking it will be more efficient to do this:

sub foo
{
  my ($self) = @_;

  if (not ref($self)) { return; }

  ...do stuff
}

Questions:

  1. Is it safe to assume that if ref() returns not undef that it will return the current package?

  2. I would ideally like to go back through and do something like this in all my methods for sanity checking. Is that a bad idea?

  3. Is there a more perlish way to do what I want?

"Use moose" is not an acceptable answer in this case. However if you are compelled to say that, please tell me how moose makes this easy or more efficient. I might want to incorporate it into my own object system.

Thanks!

EDITED to reflect that ref never returns undef, only an empty string.

EDIT 2 Here's a follow up question. Someone below suggested using:

$self->isa(__PACKAGE__)

But won't that always succeed? Unless of course the caller does something really boneheaded like:

MyClass::MyMethod($ref_to_some_other_object)

Upvotes: 3

Views: 827

Answers (3)

Eric Strom
Eric Strom

Reputation: 40152

you can also use the functional form of isa, that way you dont have to check to make sure $self is a reference. of course, the functional isa has the caveat that packages cant override isa, but i'm torn as to if that's a good or bad thing. in my own code, i usually do something like this, which i find has more useful calling semantics than UNIVERSAL::isa.

sub isa {UNIVERSAL::isa @_ > 1 ? shift : $_, @_}

.....

return unless isa $self => __PACKAGE__;

for (@objects) {
    say $_->name if isa 'Package';
}

Upvotes: 0

Dave Sherohman
Dave Sherohman

Reputation: 46187

Is it safe to assume that if ref() returns not undef that it will return the current package?

No.

my $bar = Bar->new;
Package::Foo::foo($bar);

will lead to foo putting $bar into $self and ref $self will then return Bar.

And, as already noted in the earlier answers, checking for the literal package name rather than testing isa breaks inheritance anyhow.

Upvotes: 4

Sinan Ünür
Sinan Ünür

Reputation: 118148

First, you should probably croak rather than silently doing nothing because, according to your specs, calling foo as a class method is a breach of contract.

Second, just checking if the first argument is a reference is enough. Your method will fail if there is inheritance involved:

#!/usr/bin/perl

package A;
use Carp;

sub new { bless {} => shift }
sub foo {
    croak "I am not in " . __PACKAGE__ unless __PACKAGE__ eq ref(shift)
}

package B;

use base 'A';

package main;

$x = B->new;

$x->foo;
C:\Temp> t
I am not in A at C:\Temp\t.pl line 19

See also perldoc -f ref:

If the referenced object has been blessed into a package, then that package name is returned instead. You can think of ref as a typeof operator.

So:

sub foo {
    croak "Don't call as class method" unless ref shift;
}

Finally, note that ref never returns undef.

Is it a good idea to add this check to every method? I guess one could make that argument from a design by contract view.

On the other hand, my methods assume they were called as instance methods and I only check for the possibility of a method being called as a class method if the method can provide a meaningful alternative behavior when called as such.

I cannot remember any modules which have these kinds of checks either.

By the way, instead of

sub foo {
    my ($self) = @_;

you should use

sub foo {
    my $self = shift;

leaving only the arguments to the method in @_ to be unpacked. Or, you should unpack all arguments in one fell swoop:

sub foo {
    my ($self, $bar, $baz) = @_;

Upvotes: 7

Related Questions