Search for Perl to verify that $ self is a class or object

In Perl, I just bit something like the error below:

package Foo; sub method { my $self = shift; my @args = @_; ... } 

where I called it a subroutine, not a method:

 Foo::method( "arg1", "arg2" ); 

instead of calling it as a method - in this case it was a "class method":

 Foo->method( "arg1", "arg2" ); 

A call to Foo :: method ("arg1", "arg2") caused the "arg1" to be discarded.

Similar considerations may arise with the "object method":

 my $object = Foo->new(); $obj->method( "arg1", "arg2" ); 

Is there a friendly, concise idiom of Perl to verify that the first argument, conditionally called $self , is actually an object in the class (package) and / or class / package name?

The best I came up with is:

 package Foo; sub method { my $self = ($_[0]->isa(__PACKAGE__) ? shift @_ : die "...error message..."; my @args = @_; ... } 

which is not much shorter than

 package Foo; sub method { my $self = shift; die "...error message..." if $self->isa(__PACKAGE__); my @args = @_; ... } 

or

 package Foo; use Carp::Assert; sub method { my $self = shift; assert($self->isa(__PACKAGE__)); my @args = @_; ... } 

Notes:

I know about Perl signatures, but I don't like to use experimental functions.

I know about use attributes and :method . Is this the best way to go? Similar concerns about “evolving” features.

I know about Musa - but I don’t think that Elk is imposing it. (Did I miss something.)

The problem with Perl is that there are so many ways to do something.

+7
oop perl
source share
3 answers

The best answer is not to mix functions and methods in one package. Hybrid modules are known to be problematic. Anything you might want to make a function should be a call to a class method.

In everyday programming, it should not be necessary to fully qualify the function call.


The shortest way is using Moops , which is a new way to use Moose with sugar syntax.

 use Moops; class Foo { method something() { print("something called\n"); } } Foo->new->something(); Foo::something(); # something called # Invocant $self is required at /Users/schwern/tmp/test.plx line 10. 

Moops is marked as unstable , but it is the interface, not the signatures themselves. Signatures exist and are used in production for a long time, longer than they were built. More worried about the absence was not an issue in more than a year , however the author writes good things. Your call.


Otherwise, as with anything else, write a function.

 use Carp; use Scalar::Util qw(blessed); sub check_invocant { my $thing = shift; my $caller = caller; if( !defined $thing ) { croak "The invocant is not defined"; } elsif( !ref $thing ) { croak "The invocant is not a reference"; } elsif( !blessed $thing ) { croak "The invocant is not an object"; } elsif( !$thing->isa($caller) ) { croak "The invocant is not a subclass of $caller"; } return $thing; } 

Since this returns an invocant and handles the exception for you, it can be used very briefly.

 package Foo; sub method { my $self = ::check_invocant(shift); ... } 
+10
source share

I will add to what Schwern wrote so that you can also take a look at Safe :: Isa , which allows you to safely call isa that you cannot be sure is an object.

+2
source share

I will try to follow the recommendations of @Schwern and "do not mix functions and methods in one package." However, here is an example of using the fun method approach from Function::Parameters . The example, of course, is inventive and a little inconvenient, but this illustrates the idea.

Function::Parameters requires a compiler version of at least perl5.14 . This is still perl (and XS), so it won’t magically make your code "strongly typed". But with attributes and type restrictions with Type::Tiny you can separate your methods and functions more than just the name. Even just using different names for different types of routines - fun and method by default - can be really useful.

Using the keyword ':strict' and / or the types 'method' of the default function / method ( fun => { ... } and method => { ... } below, as well as others such as method_lax ), eliminates the need passing values ​​to settings when the module is imported, so the code below can be shortened.

 use v5.22; package My::Package { use DDP; use attributes 'get'; use Function::Parameters { fun => { strict => 1, } , method => { strict => 1, invocant => 1, shift => '$class', attributes => ':method',} , } ; fun func_test ( @ ) { warn "must be called as a function" if $_[0] eq __PACKAGE__ && get(__SUB__) ne "method"; print "args = ", np @_ ; } method meth_test ( @ ) { warn "must be called as a method" unless $class eq __PACKAGE__ && get(__SUB__) eq "method"; say "\$class = $class" if length $class ; say "args = ", np @_ ; } } say "\nCalling meth_test as method:"; My::Package->meth_test( ["foo", "bar"] ); say "\nCalling meth_test as function:"; My::Package::meth_test( ["foo", "bar"] ); say "\nCalling func_test as a function:"; My::Package::func_test( qw/baz fuz/ ); say "\nCalling func_test as a method:"; My::Package->func_test( qw/baz fuz/ ); 

Exit

 Calling meth_test as method: $class = My::Package args = [ [0] [ [0] "foo", [1] "bar" ] ] Calling meth_test as function: must be called as a method at FunctionParameters-PackageCheck-SO.pl line 24. $class = ARRAY(0x801cfa330) args = [] Calling func_test as a function: args = [ [0] "baz", [1] "fuz" ] Calling func_test as a method: must be called as a function at FunctionParameters-PackageCheck-SO.pl line 17. args = [ [0] "My::Package", [1] "baz", [2] "fuz" ] 
0
source share

All Articles