How perl calls a method
Andrew Fresh
andrew@cpan.org
afresh1@openbsd.org
andrew@afresh1.com
afresh@grantstreet.com
- @AFresh1 on Twitter
I'm going to assume some basic knowledge of perl here,
but the talk hits only a few of the messy bits.
This talk is specifically perl5, but many concepts are hopefully
helpful to other languages.
I try to use very generic language and try to keep from any
specific examples of objects even though many people like to use
$fido isa Dog isa Animal and such things.
Methods are $object->method()
use My::Package;
my $object = My::Package->new();
$object->method()
Perl didn't start out object oriented so it is a bit tacked on.
This makes how they work possibly more visible than other languages.
Loads modules
package My::Package;
use v5.20;
use warnings;
use parent 'My::Parent';
1;
use parent
does two steps you used to do manually
BEGIN {
require My::Parent;
push @ISA, 'My::Parent';
}
Just the C<use parent 'My::Parent';> line.
Your classes can have multiple parents,
that's confusing, so we'll get there later.
It does these things in a BEGIN block so it happens at a specific time
That doesn't matter at all for this.
BEGIN, UNITCHECK, CHECK, INIT and END
It does a couple things we used to do manually.
C<require My::Parent> and C<push @ISA, 'My::Parent'>
The thing we care about is @ IS A but first something completely different
use parent vs use base
use parent
is just a cleaner version of use base
In any case you care about parent
is better.
Way off topic here
base.pm grew too much cruft and they rewrote the useful bits in parent.pm
to maintain backwards compatibility.
If use parent works for you, you probably want to use that.
If you know why you need to use base, then my opinion is useless.
require My::Parent
Converts My::Parent to a path
- replacing
::
with /
- appending
.pm
Which gives us My/Parent.pm
Then look in @INC
to find the file.
A side track, sort of.
Changes to a filename
Looks in @INC
@INC and %INC
We now have My/Parent.pm
And perhaps @INC = ( '/foo', '/bar' );
First look at /foo/My/Parent.pm
Then /bar/My/Parent.pm
If found, reads in the file then sets $INC{'My/Parent.pm'} = '/bar/My/Parent.pm';
@INC and %INC are global
My/Parent.pm is a path to a file
@INC is a list of directories to look in.
Obviously /foo and /bar are made up, the next slide has a real list.
%INC is a list of what we've found.
There are other ways things can get into %INC
so the filename doesn't always line up.
@INC on my machine
@INC = (
'/usr/local/libdata/perl5/site_perl/amd64-openbsd',
'/usr/libdata/perl5/site_perl/amd64-openbsd',
'/usr/local/libdata/perl5/site_perl',
'/usr/libdata/perl5/site_perl',
'/usr/libdata/perl5/amd64-openbsd/5.20.2',
'/usr/local/libdata/perl5/amd64-openbsd/5.20.2',
'/usr/libdata/perl5',
'/usr/local/libdata/perl5',
'.',
)
The @INC on my machine
came from perl -V
You can manipulate this with $ENV{PERL5LIB}, like C<PERL5LIB=./lib:./t/lib>.
push @ISA, 'My::Parent';
Back to push @ISA, 'My::Parent'
@ISA
is a list of package names we will look for methods in.
Our inheritance.
It's package scoped, actually @My::Package::ISA
Each package has one, like @My::Parent::ISA
In perl a Class is just a package that acts in a specific way.
@ISA is a list of all the classes we inherit.
It *can* contain subrefs, but that's out of scope.
It's a package scoped variable, @My::Package::ISA
Each package in perl has an ISA included. Some may be empty.
But they all inherit automatically from UNIVERSAL
Finds the Class of $object
The first thing is to figure out what we're dealing with
my $object = My::Package->new;
my $class = ref $object;
It's like $class = 'My::Package'
.
You can figure this out, there are other ways,
but an easy one is to ask what sort of reference $object is.
Blessing?
bless $scalar, $class;
$object = My::Package->new
makes $object->isa($class)
package My::Package;
use parent 'My::Parent';
sub new {
my ($class) = @_;
return bless {}, $class;
}
Another side track, blessing
perldoc -f bless
sets a bit saying this package is magical
An anonymous hashref, could be any scalar value, but usually a hashref.
Now perl knows C<< $object->isa($class) >>
All we care about is that perl knows that C<< ref $object eq $class >>
Functions are in the symbol table
There is another magical hash, %My::Package::
This is the symbol table
It holds a list of things, like package variables
and what we're interested in, functions
For example $My::Package::{my_function}->*{CODE}
Which is also available as &My::Package::my_function
C<%My::Package::>
It ends with double colons, weird, right?
*not* an instance variable.
functions are in C<< *{ $My::Package::{my_function} }{CODE} >>
or as C<< &My::Package::my_function >>
Using experimental feature 'postderef' for clarity
perldoc perlmod / perldoc perlref
Lots of confusing references and typeglobs and whatnot.
Not particularly important, because perl looks it up for us.
I am a bit confused on something as I expect C<delete $My::Package::{my_function}> to make C<< My::Package->my_function >> fail but it doesn't.
Look through @ISA
sub dfs_can ( $method, $class = __PACKAGE__ ) {
if ( my $code = \&{$class . '::' . $method} ) {
return $code;
}
foreach my $parent (@{ $class . '::ISA' }) {
my $code = dfs_can( $method, $parent );
return $code if $code;
}
die qq{Can't locate object method "$method" ...};
}
To make the code fit, I had to use experimental 'signatures'
.
If perl doesn't find &My::Package::method
It then looks in @ISA
, which happens to contain My::Parent
And we look again, for &My::Parent::method
First look for &My::Package::method
Then search @ISA, checking &My::Parent::method
This is the DFS search
*{ ${$class . '::'}{$method} }{CODE}
perldoc UNIVERSAL
mro inheritance, "dfs" vs "C3"
Method Resolution Order
- dfs
- Depth First Search
- perl's default
- C3
consistent with
- the extended precedence graph
- local precedence order
- monotonicity
Another side track, Method Resolution Order
dfs (classic) vs C3
you don't hear about what C3 means very often because it's technobabble
mro::get_linear_isa('My::Package');
perldoc mro
Classic diamond inheritance pattern
package A;
package B;
use parent 'A';
package C;
use parent 'A';
package D;
use parent ('A', 'B');
Point at the slides!
What's wrong with dfs?
Classic diamond inheritance pattern
<A>
/ \
<B> <C>
\ /
<D>
dfs looks at D B A C
That means if C overrides a method in A,
your class that uses that method will get the one from A,
not the one you want from C.
What's right with C3
Classic diamond inheritance pattern
<A>
/ \
<B> <C>
\ /
<D>
C3 looks at D B C A
consistent with the extended precedence graph,
local precedence order,
and monotonicity
aims to provide a sane method resolution order under multiple inheritance
This essentially means that no class will appear before any of its subclasses.
Now call it as My::Package::method( $object )
Perl has found the function!
And we can call it with $object as the first argument.
My::Package::method( $object );
Or maybe you found it with can
my $method = $object->can('method');
$method->( $object )
So call the fully qualified function with the first argument of $object.
Which just gives the same reference we had before.
Moose, it must be totally different
Moose extends
is pretty much the same as use parent
Moose with
Roles are another matter
- They create new packages magically
- and rebless $object into that new class
It all still works the same once it's constructed though.
Moose does all sorts of magic to construct special classes
Creates packages out of thin air and modifies the symbol table
But overall it's about the same
Finally
Load packages
Adjust @ISA
with parent
classes
Look in symbol tables for all classes in @ISA
to find $method
Call $method->( $object, @other_args )
And that's it.
We look thorough @ISA to find the method in the symbol table
*{ ${$package . '::'}{$method} }{CODE}
And then call it, putting the object as the first argument.
for more
perldoc perlobj
perldoc perlmod
perldoc UNIVERSAL
perldoc parent
perldoc mro
SUPER is important, but I didn't even mention it
here 'method' was always a string when we called it *on* an object or class
but it could be a variable containing a string or a code reference.
Thank you!
afresh1@openbsd.org
andrew@cpan.org
andrew@afresh1.com
afresh@grantstreet.com
@AFresh1 on twitter
http://cvs.afresh1.com/~andrew/talks