Use the infix class instance operator

Perl v5.32 adds Paul Evans’s infix isa operator—the “class instance operator”. One of the delightful things to note about this is addition is that it is one of the features whose development took place almost entirely through a GitHub issue and pull request. GitHub is now the primary repository for the Perl code, and has been since October 2019. This is a feature that I’ll want to use right away in new production code.

The UNIVERSAL class, from which all other classes ultimately inherit, provides the basic isa method. Pronouce that IS-A, like “a cat is a mammal”. I think the best explanation is in the now-removed Perl Barnyard OO Tutorial (perlboot). It’s also how we explain object-orientation in Intermediate Perl. We showed isa in Effective Perl Programming too.

One way to use it involves eval so that a non-object can still return false instead of a method error. If it’s not an object it doesn’t inherit:

if( eval { $var->isa( $some_package ) } ) {
	... do stuff ...

Sometimes you see this in function form to avoid the error if $var is not an object, but this subverts any overridden isa methods. Sadly, I think I use this somewhere in Effective Perl Programming too:

if( UNIVERSAL::isa( $var, $some_package ) ) {
	... do stuff ...

This new infix feature removes some of the syntax and obviates the eval by returning false if $var is not an object:

use v5.31.7;
use warnings;
use feature qw(isa);
no warnings qw(experimental::isa);

if( $var isa $some_package ) {
	... do stuff ...

The lefthand side can be anything. If it’s not an object, it’s false. If it’s an object that inherits from the class name on the righthand side, it’s true:

use v5.31.7;
use warnings;
use feature qw(isa);
no warnings qw(experimental::isa);

package Parent { sub new { bless {}, $_[0] } }
package Child  { our @ISA = qw(Parent) }

my $obj = Child->new;

my @tests = (
	[ 'string',   'Some::Class',  'Plain string'            ],
	[ 0xDEADBEEF, "Some::Class",  'Plain number'            ],
	[ undef,      "Some::Class",  'undef'                   ],
	[ $obj,       "Parent",       'obj of Parent'           ],
	[ $obj,       "Child",        'obj of Child'            ],
	[ $obj,       "UNIVERSAL",    'obj of UNIVERSAL'        ],
	[ $obj,       "Other::Class", 'not obj of Other::Class' ],
	[ [],         "ARRAY",        'array ref'               ],
	[ {},         "HASH",         'hash ref'                ],

foreach my $test ( @tests ) {
	my( $candidate, $class, $label ) = $test->@*;

	say $label if $candidate isa $class;

say "Loaded class, bareword, Parent" if $obj isa Parent;
say "Loaded class, bareword, Child"  if $obj isa Child;

# Normally, a bareword class needs to be loaded. This ignores
# that.
say "Non-loaded class, bareword" if $obj isa Other::Class;

say "Done!";

This is the entire output, warnings (none!) and all. The program goes all the way to the end without die-ing, even though I use a class name that I haven’t defined:

obj of Parent
obj of Child
Loaded class, bareword, Parent
Loaded class, bareword, Child

You can see more examples in the test file, t/op/isa.t.

I think there’s some indication that if this works out, we might get the same thing for can and does.

Things to Remember

  • UNIVERSAL::isa subverts overridden isa methods.
  • Calling the isa blows up if the invocant is not an object.
  • The experimental infix isa solves these problems.
Leave a comment


Leave a Reply

[ Ctrl + Enter ]