Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

perl expression "or" and subroutine "isa"

Tags:

perl

I encountered this code, but I don't understand it .

$type->isa('UNIVERSAL')
  or eval "require $type"
    or croak $@;
  1. 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?

  2. I am confused about the two or terms. What do they mean?

like image 490
wuchang Avatar asked May 22 '13 06:05

wuchang


3 Answers

Chained or melody

Perl’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:

  1. $type->isa('UNIVERSAL')
  2. eval "require $type"
  3. 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.

Has a certain module been loaded?

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.

Does a certain package exist?

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
like image 173
Greg Bacon Avatar answered Nov 16 '22 13:11

Greg Bacon


  1. 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.

  2. 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.

like image 26
Borodin Avatar answered Nov 16 '22 13:11

Borodin


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.
  • low precendence means that the expression 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, and
C 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 $@;
    }
 }
like image 2
Déjà vu Avatar answered Nov 16 '22 14:11

Déjà vu