I encountered this code, but I don't understand it .
$type->isa('UNIVERSAL')
or eval "require $type"
or croak $@;
I have refered to perldoc
and I know that the subroutine isa()
is checking whether $type
has been blessed to the package UNIVERSAL
. But in fact, all Perl classes are subclasses of UNIVERSAL
, right?
I am confused about the two or
terms. What do they mean?
or
melodyPerl’s or
operator works just like ||
but has very low precedence, that is, how “tightly” it binds to surrounding code. Logical Or in Perl short-circuits, that is evaluation stops as soon as an operand evaluates true.
A common idiom in Perl uses short-circuiting for flow control, often related to error handling as in
open my $fh, "<", $path or die "$0: open: $!";
On success, open
returns a true value. The result of or
true when at least one of its operands is true, so on a successful open
, perl does not die
because the right-hand operand to or
is not evaluated.
Chains of or
operators can be extended arbitrarily. This is useful for specifying defaults. In the simple case, that looks like
my $name = shift or "Bruce";
If you have multiple fallback cases, put them in the order you want to try them.
my $name = shift or given_name $family_name or "Bruce";
The first case to succeed wins.
The code
$type->isa('UNIVERSAL')
or eval "require $type"
or croak $@;
is such a chain. Evaluation proceeds in the following order:
$type->isa('UNIVERSAL')
eval "require $type"
croak $@
If $type
is the name of a package that already exists, then steps 2 and 3 are ignored.
Reaching step 2 means $type
is not loaded, so now the code attempts to require
it. If require
succeeds, step 3 is ignored.
Reaching step 3 means the package does not exist and it could not be loaded. In this case, croak
outputs the reason for the failure and exits the program.
When called as a method, isa
has three forms:
$obj->isa( TYPE )
CLASS->isa( TYPE )
eval { VAL->isa( TYPE ) }
The third is a generic catch-all that is not relevant here. The first is what you described in your question.
The second is documented as
When used as a class method (
CLASS->isa( TYPE )
), sometimes referred to as a static method),isa
returns true if CLASS inherits from (or is itself) the name of the package TYPE or inherits from package TYPE.
The line just before the or
chain in ::_factory
, from SOAP::WSDL::XSD::TypeLib::ComplexType, is
my $type = $CLASSES_OF{ $class }->{ $name }
or croak "No class given for $name";
Here, the value of $type
is a string, so isa
is checking whether the class named $type
inherits from UNIVERSAL. As you noted in your question, all classes inherit from UNIVERSAL.
So what’s going on? If isa
fails, the code calls require
, so the intent of the isa
check must be to determine whether a given class has been loaded.
Remember that Perl classes and Perl packages are closely related. For a class to exist, a package by the same name must exist. Detecting non-destructively whether a package exists turns out to be tricky. Probing the stash %Foo::Bar::Baz::Quux::
directly autovivifies. Perl stores stashes hierarchically, so you’d have to go spelunking as in
sub package_exists {
my($pkg) = @_;
$pkg =~ s/::$//;
my @parts = split /::/, $pkg;
my $stash = $main::{"main::"};
while (@parts) {
my $subpkg = shift(@parts) . "::";
return unless exists $stash->{$subpkg};
$stash = $stash->{$subpkg};
}
$stash;
}
A successful require
modifies a special hash named %INC. I’m not sure why the code doesn’t check %INC
. Perhaps some types are loaded outside the require mechanism, or maybe the author was pleased with this delightful little hack.
Consider the source of sv_derived_from
, which implements isa
.
bool
Perl_sv_derived_from_pvn(pTHX_ SV *sv,
const char *const name, const STRLEN len,
U32 flags)
{
dVAR;
HV *stash;
PERL_ARGS_ASSERT_SV_DERIVED_FROM_PVN;
SvGETMAGIC(sv);
if (SvROK(sv)) {
const char *type;
sv = SvRV(sv);
type = sv_reftype(sv,0);
if (type && strEQ(type,name))
return TRUE;
stash = SvOBJECT(sv) ? SvSTASH(sv) : NULL;
}
else {
stash = gv_stashsv(sv, 0);
if (!stash)
stash = gv_stashpvs("UNIVERSAL", 0);
}
return stash ? isa_lookup(stash, name, len, flags) : FALSE;
}
Keep in mind that we are invoking this code with
"SomeClass"->isa("UNIVERSAL")
so sv
contains the string "SomeClass"
. SvROK
checks whether sv
is a reference, which a string is not, so we are always in the else
branch.
At the end, the value of stash
will always be non-NULL
: pointing to the symbol table of either the package if it has been loaded or UNIVERSAL otherwise. Lookup succeeds for loaded packages and fails otherwise. Try it yourself:
$ perl -le 'print "strict"->isa("UNIVERSAL") ? "loaded" : "not loaded"' not loaded $ perl -Mstrict -le 'print "strict"->isa("UNIVERSAL") ? "loaded" : "not loaded"' loaded
isa
is being misused here to check whether a given package has been imported with require
or use
. Package->isa('UNIVERSAL')
will return true if packcage Package
exists, otherwise false.
or
is being used in a short circuit
capacity. In expression a or b
, b
will not be evaluated if a
has a true value. This makes no difference if the terms are variables, but if they are subroutine calls that have side-effects then it becomes an alternative to the if
statement.
The statement in question is equivalent to this code
unless ($type->isa('UNIVERSAL')) {
unless (eval "require $type") {
croak $@;
}
}
The overall effect is to check whether the module specified by the variable $type
has been imported. If not then require
is used to import it, and croak
is called if that fails.
Question 1
UNIVERSAL is the base class for all classes in Perl.
Question 2
or
in Perl is a low precedence operator, meaning
A or B
is true if A
is true, or if B
is true, meaning, if either of the two conditions is true.A
above is evaluated before the or
, provided that A
operators precedence is higher than or
. Which is likely to be the case as or
(unlike ||
) has the lowest precedence in Perl.In Perl, given the expression
A or B or C;
B
is processed (evaluated) only if A
is false, andC
is processed (evaluated) only if B
is false.
Practically in your case, if $type->isa('UNIVERSAL')
is true, Perl stops here and considers the whole expression true. If false, it will evaluate eval "require $type"
: if true the whole expression is true, if false, it finally evaluates croak $@
.
This is a quick way to do
if ( ! $type->isa('UNIVERSAL')) {
if ( ! eval "require $type") {
croak $@;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With