Skip to content
VLSI Mentor

Perl Part I · Module 12 · Modules and Packages

Reading Object-Oriented Perl You Did Not Write

Most object-oriented Perl you will deal with is code somebody else wrote, often years ago, spread over several files, and using one of three or four different conventions for the same ideas. The previous chapter gives you the mechanism — a blessed reference and a calling convention — and this one turns that into a procedure for finding your way around. The hard question in inherited code is never what the syntax means; it is where a given method is actually defined, because a method call that works may be running a subroutine three classes up a chain you have not finished mapping. Section three answers that question properly: instead of grepping a directory and hoping, you ask the running program which code a method call would reach and which package owns it. The chapter also covers the declarative systems, measured rather than described — Moo turned out to be installed on the machine this page was built with — and ends the module by confirming that a Moo object is still the blessed hash reference Chapter 12.12 measured.

Advanced16 min readPerlPart IModules

Part I · Module 12 · Chapter 12.13 · Reading Object-Oriented Perl You Did Not Write

1. Finding the Constructor, and Following the Chain

Azvya Education Pvt. Ltd.VLSI Mentor
inheritance.pl
use strict;
use warnings;

package VLSI::Path {
    sub new  { my ($c,%a)=@_; return bless {%a}, $c }
    sub pin  { my ($s)=@_; return $s->{pin} }
    sub kind { return "generic path" }
    sub describe { my ($s)=@_; return $s->kind . " at " . $s->pin }
}

package VLSI::ClockPath {
    use parent -norequire, 'VLSI::Path';
    sub kind { return "clock path" }        # overrides
}

my $g = VLSI::Path->new(pin => "u_alu/A");
my $c = VLSI::ClockPath->new(pin => "u_clk/CK");

printf "generic : %s\n", $g->describe;
printf "clock   : %s\n", $c->describe;
printf "clock isa VLSI::Path : %s\n", ($c->isa('VLSI::Path') ? "yes" : "no");
printf "\@VLSI::ClockPath::ISA : %s\n", join(",", @VLSI::ClockPath::ISA);
print  "\ndescribe() is INHERITED but calls kind(), which the subclass REPLACED\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
generic : generic path at u_alu/A
clock   : clock path at u_clk/CK
clock isa VLSI::Path : yes
@VLSI::ClockPath::ISA : VLSI::Path

describe() is INHERITED but calls kind(), which the subclass REPLACED

VLSI::ClockPath defines one subroutine, kind. It has no new and no describe, and both work — because use parent put VLSI::Path in its @ISA, and a method Perl cannot find in a class it looks for in that class's parents.

The second output line is the part that makes inherited code hard to read. describe is defined only in the parent, and its text says $s->kind. Called on a clock path, it returned "clock path at u_clk/CK" — the parent's method ran the subclass's kind. That is dynamic dispatch: a method call inside a parent resolves against the actual class of the object, not against the class the code was written in.

So reading the parent's source tells you what describe does structurally and not what it will produce. Any method it calls on $self may have been replaced.

Three forms of inheritance declaration all mean the same thing, and you will meet all three:

  • our @ISA = ('Parent'); — the underlying mechanism, and what the other two set.
  • use parent 'Parent'; — the modern form; it also requires the parent's file, which is usually what you want. -norequire suppresses that, needed above because the parent is in the same file.
  • use base 'Parent'; — the older form, equivalent for practical purposes.

When orienting yourself in an unfamiliar class, read @ISA first. It tells you where the rest of the behaviour is.

2. Reading a Constructor

The constructor is where the fields are, and it is the first thing to find, because Perl has no field declarations — the only way to know what an object contains is to read what the constructor puts in it.

Azvya Education Pvt. Ltd.VLSI Mentor
super-call.pl
use strict;
use warnings;

package Base {
    sub new { my ($c,%a)=@_; return bless { base => 1, %a }, $c }
    sub describe { my ($s)=@_; return "base(" . join(",", sort keys %$s) . ")" }
}
package Derived {
    our @ISA = ('Base');
    sub new {
        my ($class, %args) = @_;
        my $self = $class->SUPER::new(%args);   # call the parent's constructor
        $self->{derived} = 1;
        return $self;
    }
    sub describe { my ($s)=@_; return "derived[" . $s->SUPER::describe() . "]" }
}

my $d = Derived->new(pin => "u_alu/A");
printf "ref        : %s\n", ref($d);
printf "describe   : %s\n", $d->describe;
printf "has base   : %s\n", ($d->{base} ? "yes" : "no");
printf "has derived: %s\n", ($d->{derived} ? "yes" : "no");
print  "\nSUPER:: calls the method the PARENT would have run\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
ref        : Derived
describe   : derived[base(base,derived,pin)]
has base   : yes
has derived: yes

SUPER:: calls the method the PARENT would have run

The object has fields from both constructors: base from the parent, derived from the subclass, and pin from the arguments. The describe output derived[base(base,derived,pin)] shows all three keys and shows the nesting.

SUPER:: is the construct to recognise. $class->SUPER::new(%args) means "call new as the parent class would" — it looks up the method starting from the parent of the package the code is written in, skipping the current class's own version and so avoiding infinite recursion.

The pattern my $self = $class->SUPER::new(%args); ...add fields...; return $self; is the standard hand-written subclass constructor, and reading it tells you to go and read the parent's constructor too. A field you cannot find in the class you are looking at is usually set one level up.

Note also that ref($d) is Derived, not Base, even though the actual bless happens in Base::new. That is the bless $self, $class habit from Chapter 12.12 working: the parent blessed into whatever class name was passed in.

3. Asking the Program Where a Method Lives

This is the technique worth taking from the chapter, and it replaces a directory-wide grep with a question.

Azvya Education Pvt. Ltd.VLSI Mentor
where-is-it-defined.pl
use strict;
use warnings;

package Base    { sub new { bless {}, shift } sub only_base { 1 } sub shared { "base" } }
package Middle  { our @ISA = ('Base');   sub shared { "middle" } }
package Leaf    { our @ISA = ('Middle'); }

my $obj = Leaf->new;

# can() returns the CODE REF that would actually run.
for my $m (qw(only_base shared new nonexistent)) {
    my $code = $obj->can($m);
    if (!$code) { printf "%-12s : not found\n", $m; next }
    # Devel::Peek is overkill; B::svref_2object names the owning package.
    require B;
    my $gv = B::svref_2object($code)->GV;
    printf "%-12s : defined in %s\n", $m, $gv->STASH->NAME;
}
printf "\n\@Leaf::ISA   : %s\n", join(",", @Leaf::ISA);
printf "\@Middle::ISA : %s\n", join(",", @Middle::ISA);
print  "shared() resolves to Middle, not Base -- the FIRST match up the chain wins\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
only_base    : defined in Base
shared       : defined in Middle
new          : defined in Base
nonexistent  : not found

@Leaf::ISA   : Middle
@Middle::ISA : Base
shared() resolves to Middle, not Base -- the FIRST match up the chain wins

Four questions, four definite answers. Called on a Leaf object: only_base lives in Base, new lives in Base, and shared lives in Middle — not Base, even though Base also defines it, because Perl takes the first match walking up the chain.

The mechanism is two steps, and both are worth knowing separately:

$obj->can($name) returns the code reference that a call to $obj->$name(...) would actually reach, after all the inheritance has been resolved — or false if there is none. Chapter 12.12 used it as a yes/no test; here the value is the useful part.

B::svref_2object($code)->GV->STASH->NAME takes that code reference and names the package it was compiled in. B is a core module, so this needs nothing installed.

Together they answer "where is this method really defined" from inside the running program, which is strictly better than grepping for sub shared because:

  • It accounts for inheritance, so you get the one that wins rather than all the candidates.
  • It accounts for the actual load path, so you get the file Perl really loaded rather than another copy on disk — the %INC problem from Chapter 12.4 in a new form.
  • It finds methods that grep cannot, because they were generated at run time rather than written out. That is exactly what the declarative systems in the next section do.

4. The Declarative Systems

Azvya Education Pvt. Ltd.VLSI Mentor
moo-attributes.pl
use strict;
use warnings;

package VLSI::MooPath {
    use Moo;
    has pin   => (is => 'ro', required => 1);
    has slack => (is => 'rw', default  => sub { 0 });
    sub is_violation { my ($self) = @_; return $self->slack < 0 ? 1 : 0 }
}

my $p = VLSI::MooPath->new(pin => "u_alu/A", slack => -0.12);

printf "pin          : %s\n", $p->pin;
printf "slack        : %s\n", $p->slack;
printf "is_violation : %d\n", $p->is_violation;
printf "ref          : %s\n", ref($p);

# 'rw' means there is a setter; 'ro' means there is not
$p->slack(0.44);
printf "after set    : %s\n", $p->slack;
my $ro = eval { $p->pin("other"); 1 };
printf "writing a ro attribute : %s\n", ($ro ? "allowed" : "rejected");

# a required attribute that is missing is a constructor error
my $missing = eval { VLSI::MooPath->new(slack => 1); 1 };
printf "omitting a required attr : %s\n", ($missing ? "allowed" : "rejected");

# the object is STILL a blessed hash underneath
use Scalar::Util qw(reftype blessed);
printf "reftype      : %s   blessed : %s\n", reftype($p), blessed($p);
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
pin          : u_alu/A
slack        : -0.12
is_violation : 1
ref          : VLSI::MooPath
after set    : 0.44
writing a ro attribute : rejected
omitting a required attr : rejected
reftype      : HASH   blessed : VLSI::MooPath

That class has no new and no accessors written out, and all of them work. Moo generated them from the two has declarations.

Reading a has line is most of what reading a Moo or Moose class involves, and the options you will see constantly are all visible above:

  • is => 'ro' generates a reader only. Writing to it was rejected — which is the field-level protection Chapter 12.12 said plain bless does not give you.
  • is => 'rw' generates a reader and a writer, called as $p->slack(0.44).
  • required => 1 makes the constructor fail if the attribute is absent — also rejected above.
  • default => sub { ... } supplies a value when none is given. It is a code reference rather than a plain value so that each object gets a fresh one, which matters for a reference.

Two more you will meet: lazy => 1 defers the default until first read, and builder => '_build_name' names a method to compute it.

The last line closes the module. reftype is HASH and blessed is VLSI::MooPath — a Moo object is exactly the blessed hash reference Chapter 12.12 measured. Nothing new was added to the language; has is a subroutine that writes accessors into the package at compile time. That is why the technique in section three finds those generated methods when grep cannot.

Both Moo and Moose are installed on the machine this page was built with, which is worth checking before assuming either is available on a farm. Moose is the full system and is slow to load; Moo is the lightweight subset and is the usual choice now. Class::Accessor is an older generator you will also see. All three produce blessed hash references.

5. A Procedure

Putting it together, for a class you have never seen:

Read @ISA, use parent or use base first. That tells you how many files you are really dealing with.

Find the constructor and read what it puts in the hash. With no field declarations, that is the only inventory of the object's contents. Follow any SUPER::new upward.

Scan for has lines. If they are there, the fields are declared after all, and ro versus rw tells you which are meant to change.

For any method whose behaviour surprises you, ask where it is defined with can plus B::svref_2object. Do not assume the class you are reading is the one that will run.

Remember that a parent's method may call a child's override. Chapter one of this chapter measured it: reading the parent tells you the structure, not the result.

Treat the fields as public. $obj->{field} works regardless of ro, because ro only means no setter was generated. Chapter 12.12's measurement applies to Moo objects too.

6. What To Remember

  • Read @ISA first — our @ISA, use parent and use base all set it, and it tells you where the rest of the behaviour lives.
  • A parent's method can call a child's override. Measured: describe, defined only in the parent, produced "clock path" for a subclass that replaced kind.
  • The constructor is the field inventory. There are no declarations in hand-written OO Perl, so what new puts in the hash is all there is.
  • SUPER:: calls the parent's version, and my $self = $class->SUPER::new(%args) is the standard subclass constructor — follow it upward.
  • ref reports the class that was passed in to bless, which is why bless $self, $class matters.
  • $obj->can($name) returns the code reference that would actually run, and B::svref_2object($code)->GV->STASH->NAME names the package that owns it. Measured across three levels, including shared resolving to the middle class rather than the base.
  • That beats grep: it resolves inheritance, uses the file actually loaded, and finds generated methods.
  • The first match walking up the chain wins.
  • A has line declares an attribute: ro reader only, rw reader and writer, required enforced by the constructor, default as a code reference.
  • A Moo object is still a blessed hash. reftype HASH, blessed the class — and the fields stay reachable as $obj->{field} whatever ro says.

That completes Module 12. Thirteen chapters: why code becomes a module, the package that makes it possible, the two ways a caller loads one and the search path that finds it, the file itself and what it must contain, the export policy that decides what callers may use, the three scoping keywords and what each one exposes, the core modules that mean you need write less, the installation constraint a shared farm imposes, private module state, and finally the two pieces Perl's object system is actually made of. Module 13 turns to reading and writing files, which is where almost everything in Modules 11 and 12 gets applied.

Where this fits

Part of the Perl curriculum.