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
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";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 REPLACEDVLSI::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 alsorequires the parent's file, which is usually what you want.-norequiresuppresses 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.
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";ref : Derived
describe : derived[base(base,derived,pin)]
has base : yes
has derived: yes
SUPER:: calls the method the PARENT would have runThe 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.
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";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 winsFour 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
%INCproblem 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
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);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::MooPathThat 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 plainblessdoes not give you.is => 'rw'generates a reader and a writer, called as$p->slack(0.44).required => 1makes 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
@ISAfirst —our @ISA,use parentanduse baseall 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 replacedkind. - The constructor is the field inventory. There are no declarations in hand-written OO Perl, so what
newputs in the hash is all there is. SUPER::calls the parent's version, andmy $self = $class->SUPER::new(%args)is the standard subclass constructor — follow it upward.refreports the class that was passed in tobless, which is whybless $self, $classmatters.$obj->can($name)returns the code reference that would actually run, andB::svref_2object($code)->GV->STASH->NAMEnames the package that owns it. Measured across three levels, includingsharedresolving 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
hasline declares an attribute:roreader only,rwreader and writer,requiredenforced by the constructor,defaultas a code reference. - A Moo object is still a blessed hash.
reftypeHASH,blessedthe class — and the fields stay reachable as$obj->{field}whateverrosays.
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.
