Share
Embed
Perl 5 MOP
This is a talk given at YAPC::EU 2012 in Frankfurt Germany on the proposed Meta Object Protocol for Perl 5
Stevan Little
August 22, 2012
Transcript
87 slides
-
Perl 5 MOP Stevan Little • YAPC::EU 2012 • Frankfurt,
Germany Wednesday • 22 August 2012 So I have been kicking around some Larry quotes in talks recently. And I decided I wanted to show 3 of my favorites here. But before I did this I asked Larry about each one (mostly to be sure he really said them). -
Perl was always designed to be an evolving language. –
Larry Wall So I asked Larry about this one he told me basically that “adding sigils to variables allowed for builtins to be added and not conflict with variables” , just think about that for a moment. Cool right? -
Perl 5 was my rewrite of Perl. I want Perl
6 to be the community's rewrite of Perl and of the community. – Larry Wall When I parse this sentence, I only find one community -
The whole intent of Perl 5's module system was to
encourage the growth of Perl culture rather than the Perl core. – Larry Wall * Historical tidbit: Perl 4 had oraperl, etc, which split the community, Larry saw this and didnt want this to happen, so saw that modules were critical way to allowed the community to be lexically scoped * I think this conference and the others like it, YAPCs and Workshops and Mongers groups are an example of the success of this plan. -
last summer Jesse Vincent gave this talk at ... YAPC::NA,
then again at YAPC::EU, then at OSCON, and then at YAPC::Asia, Jesse gave this talk and said a lot of interesting things. -
“Perl 5.16 and Beyond” by Jesse Vincent last summer Jesse
Vincent gave this talk at ... YAPC::NA, then again at YAPC::EU, then at OSCON, and then at YAPC::Asia, Jesse gave this talk and said a lot of interesting things. -
“Perl 5.16 and Beyond” by Jesse Vincent ‣@yapc::na last summer
Jesse Vincent gave this talk at ... YAPC::NA, then again at YAPC::EU, then at OSCON, and then at YAPC::Asia, Jesse gave this talk and said a lot of interesting things. -
“Perl 5.16 and Beyond” by Jesse Vincent ‣@yapc::na ‣@yapc::eu last
summer Jesse Vincent gave this talk at ... YAPC::NA, then again at YAPC::EU, then at OSCON, and then at YAPC::Asia, Jesse gave this talk and said a lot of interesting things. -
“Perl 5.16 and Beyond” by Jesse Vincent ‣@yapc::na ‣@yapc::eu ‣@oscon
last summer Jesse Vincent gave this talk at ... YAPC::NA, then again at YAPC::EU, then at OSCON, and then at YAPC::Asia, Jesse gave this talk and said a lot of interesting things. -
“Perl 5.16 and Beyond” by Jesse Vincent ‣@yapc::na ‣@yapc::eu ‣@oscon
‣@yapc::asia last summer Jesse Vincent gave this talk at ... YAPC::NA, then again at YAPC::EU, then at OSCON, and then at YAPC::Asia, Jesse gave this talk and said a lot of interesting things. -
Core ↝ Modules the idea put forth was to move
things into modules, where they can be loaded as needed, but not burden the core perl-guts with them. I used this neat unicode wavy arrow because there is a certain amount of handwaving here. Largely because he also talks about preserving past version semantics and other not so simple things. -
a simpler language is a more hackable language Does this
mean we loose some part of what Perl is? Hell No, thats what the CPAN is for! -
MOP Lets first talk about what is a MOP. Meta
Object Protocol -
Classes, Methods, Attributes & Instances MOP is just an API
for these things. Note the uppercase, it is important. Class is not the same thing as class. -
... an abstraction of a system of abstractions that is
used to build abstractions. So, when my mother asks me “what is all this Moose stuff you do”, this is what I tell her. -
... an abstraction of a system of abstractions that is
used to build abstractions. an abstraction (the MOP) -
... an abstraction of a system of abstractions that is
used to build abstractions. system of abstractions (classes, methods, etc.) -
... an abstraction of a system of abstractions that is
used to build abstractions. build abstractions (your classes) -
*{$pkg . '::foo'} = \&bar; this is the original MOP,
little akward in places, but it works. I know this because i had to use this MOP to write Moose with! -
So this is actually a picture of the p5-mop meta-layer.
We are side stepping Module and Package for the moment so that we can focus on the class layer. However we might just keep it this way since tools like Package::Stash exist -
class Point { has $x = 0; has $y =
0; method clear { ($x, $y) = (0, 0); } } -
class Point { has $x = 0; has $y =
0; method clear { ($x, $y) = (0, 0); } } class keyword - if no superclass, inherits from Object by default -
class Point { has $x = 0; has $y =
0; method clear { ($x, $y) = (0, 0); } } has keyword - "instance-lexical" scoping of attributes -
class Point { has $x = 0; has $y =
0; method clear { ($x, $y) = (0, 0); } } method keyword - the attributes are available in methods as lexical variables - the scope of them being carried around in the instance - the destructuring bind in the clear method -
my $point = Point->new( x => 10, y => 20
); instance creation is pretty straight forward -
my $point = Point->new( x => 10, y => 20
); we didn't have to write a &new method, because we inherited from Object -
my $point = Point->new( x => 10, y => 20
); - constructor takes name/value pairs of the attributes (reasonably good default that can be changed via the MOP) -
class Point3D (extends => Point) { has $z = 0;
method clear { super; $z = 0; } } extend a class -
class Point3D (extends => Point) { has $z = 0;
method clear { super; $z = 0; } } the extends syntax -
class Point3D (extends => Point) { has $z = 0;
method clear { super; $z = 0; } } the super syntax calling the superclass-method -
class BankAccount { has $balance ( is => 'ro' )
= 0; method deposit ($amount) { $balance += $amount } method withdraw ($amount) { die "Account overdrawn" if ($balance <= $amount); $balance -= $amount; } } -
class BankAccount { has $balance ( is => 'ro' )
= 0; method deposit ($amount) { $balance += $amount } method withdraw ($amount) { die "Account overdrawn" if ($balance <= $amount); $balance -= $amount; } } build in support for ro, rw and wo accessors (TODO) - this is actually a metadata expression - which is like annotations (Java), attributes (C//), decorators (Python) - it is executed at compile time and passed to the meta object being created - setting the default values of attributes -
class BankAccount { has $balance ( is => 'ro' )
= 0; method deposit ($amount) { $balance += $amount } method withdraw ($amount) { die "Account overdrawn" if ($balance <= $amount); $balance -= $amount; } } Isn’t it nice how clean the += and -= makes things -
class BankAccount { has $balance ( is => 'ro' )
= 0; method deposit ($amount) { # $self->balance( # $self->balance + $amount # ); $balance += $amount } method withdraw ($amount) { # my $balance = $self->balance; die "Account overdrawn" if ($balance <= $amount); # $self->balance( $balance + $amount ); $balance -= $amount; } } And guess what, that is 4 less method calls - note there is no reference to $self in this entire class -
class BankAccount { has $balance ( is => 'ro' )
= 0; method deposit ($amount) { $balance += $amount } method withdraw ($amount) { die "Account overdrawn" if ($balance <= $amount); $balance -= $amount; } } simple signatures on methods -
class CheckingAccount (extends => BankAccount) { has $overdraft_account ( is
=> 'rw' ); method withdraw ($amount) { my $overdraft_amount = $amount - $self->balance; if ( $overdraft_account && $overdraft_amount > 0 ) { $overdraft_account->withdraw( $overdraft_amount ); $self->deposit( $overdraft_amount ); } super( $amount ); } } -
class CheckingAccount (extends => BankAccount) { has $overdraft_account ( is
=> 'rw' ); method withdraw ($amount) { my $overdraft_amount = $amount - $self->balance; if ( $overdraft_account && $overdraft_amount > 0 ) { $overdraft_account->withdraw( $overdraft_amount ); $self->deposit( $overdraft_amount ); } super( $amount ); } } - $balance is not accessible in the subclass methods - attributes are completely private -
class CheckingAccount (extends => BankAccount) { has $overdraft_account ( is
=> 'rw' ); method withdraw ($amount) { my $overdraft_amount = $amount - $self->balance; if ( $overdraft_account && $overdraft_amount > 0 ) { $overdraft_account->withdraw( $overdraft_amount ); $self->deposit( $overdraft_amount ); } super( $amount ); } } Make a note here about where we differ from Moose, in Moose the super call will actually pass @_ through for you and not allow you to change it. Here actually w do do that, this actually behaves much more like SUPER pseudo package , you have to pass variables down yourself -
role Equality { method equal_to; method not_equal_to ($other) { not
$self->equal_to($other); } } we also have roles, horray! - apply this role to something you might wish to be able to test equality -
role Equality { method equal_to; method not_equal_to ($other) { not
$self->equal_to($other); } } methods with no body are 'required' methods -
role Equality { method equal_to; method not_equal_to ($other) { not
$self->equal_to($other); } } and we supply one method, which just negates the equal_to check that a consuming client will be required to implement -
role Comparable (with => Equality) { method compare; method equal_to
($other) { $self->compare($other) == 0 } method greater_than ($other) { $self->compare($other) == 1 } method less_than ($other) { $self->compare($other) == -1 } } roles can consume other roles ... -
role Comparable (with => Equality) { method compare; method equal_to
($other) { $self->compare($other) == 0 } method greater_than ($other) { $self->compare($other) == 1 } method less_than ($other) { $self->compare($other) == -1 } } -
role Comparable (with => Equality) { method compare; method equal_to
($other) { $self->compare($other) == 0 } method greater_than ($other) { $self->compare($other) == 1 } method less_than ($other) { $self->compare($other) == -1 } } here, we add one more required method, and again implement the role in terms of that method -
role Printable { method as_string; } roles can be just
interfaces too -
class DE::Currency (with => [ Comparable, Printable ]) { has
$amount ( is => 'rw' )= 0; method compare ($other) { $amount <=> $other->amount } method as_string { sprintf '€ %0.2f' => $amount } } classes can consume roles (more then one if they want) -
class DE::Currency (with => [ Comparable, Printable ]) { has
$amount ( is => 'rw' )= 0; method compare ($other) { $amount <=> $other->amount } method as_string { sprintf '€ %0.2f' => $amount } } -
class DE::Currency (with => [ Comparable, Printable ]) { has
$amount ( is => 'rw' )= 0; method compare ($other) { $amount <=> $other->amount } method as_string { sprintf '€ %0.2f' => $amount } } and we are implementing the compare from the Comparable role -
class DE::Currency (with => [ Comparable, Printable ]) { has
$amount ( is => 'rw' )= 0; method compare ($other) { $amount <=> $other->amount } method as_string { sprintf '€ %0.2f' => $amount } } and we are implementing the Printable role -
class Foo { has $bar; method bar { $baz };
} - spelling errors on slots are compile time errors - similar to inside-out objects, but less cumbersome -
package Foo; use strict; use warnings; class Bar { has
$baz; method foo { $baz } } packages can contain classes - because classes are first class citizens just like subs, etc. - this is much like other langauges (Java, etc) - classes don't need strict and warnings - it would be on by default -
my $foobar = Foo::Bar->new( baz => 'gorch' ); ... and
they are called just as you would expect -
package MyApp::Reports; use strict; use warnings; our $CONFIG = MyApp::Config->load;
sub create_dbh { ... } class Finance { has $money_to_make = 10_000_000; method create_report { my $dbh = create_dbh; # ... $self->print_report( $CONFIG ); } } -
package MyApp::Reports; use strict; use warnings; our $CONFIG = MyApp::Config->load;
sub create_dbh { ... } class Finance { has $money_to_make = 10_000_000; method create_report { my $dbh = create_dbh; # ... $self->print_report( $CONFIG ); } } - the class has access to the package scope as well -
package MyApp::Reports; use strict; use warnings; our $CONFIG = MyApp::Config->load;
sub create_dbh { ... } class Finance { has $money_to_make = 10_000_000; method create_report { my $dbh = create_dbh; # ... $self->print_report( $CONFIG ); } } - this means static class scoped data is simple -
package MyApp::Reports; use strict; use warnings; our $CONFIG = MyApp::Config->load;
sub create_dbh { ... } class Finance { has $money_to_make = 10_000_000; method create_report { my $dbh = create_dbh; # ... $self->print_report( $CONFIG ); } } - this means you can have subs that are not part of the class namespace -
package MyApp::Reports; use strict; use warnings; our $CONFIG = MyApp::Config->load;
my sub create_dbh { ... } class Finance { has $money_to_make = 10_000_000; method create_report { my $dbh = create_dbh; # ... $self->print_report( $CONFIG ); } } And, likely in 5.18 or 5.20, there will be private subs, so now you have private subs, they are not methods, but they are inaccessible from outside of the package and they are not part of the class’s dispatch space -
package DB::FlatFile; use Path::Class qw[ file ]; class DataFile {
has $path; has $file; has $data ( is => 'ro' ); BUILD { $file = file( $path ); $data = [ $file->slurp(chomp => 1) ]; } } Here is an example of using this with exported functions -
package DB::FlatFile; use Path::Class qw[ file ]; class DataFile {
has $path; has $file; has $data ( is => 'ro' ); BUILD { $file = file( $path ); $data = [ $file->slurp(chomp => 1) ]; } } - import functions into your package, - this removes the need to import anything into your class namespace. - no more namespace::clean hacks -
package DB::FlatFile; use Path::Class qw[ file ]; class DataFile {
has $path; has $file; has $data ( is => 'ro' ); BUILD { $file = file( $path ); $data = [ $file->slurp(chomp => 1) ]; } } - this also shows BUILD - works mostly just like Moose does (the dispatch order anyway) - however both BUILD and DEMOLISH are not in the dispatch path (which is a good thing) -
use Fun; fun fibonacci ( $i ) { given (
$i ) { when ( $i == 0 ) { 0 } when ( $i == 1 ) { 1 } default { fibonacci( $i - 1 ) + fibonacci( $i - 2 ) } } } Before I go into the next example, I want to show ... Fun -
use Fun; fun reduce ( $acc, $h, @tail ) {
return $acc + $h unless @tail; return reduce( $acc + $h, @tail ); } -
use Fun; fun convert_error ($err, $handle) { given ( $err
) { when ( qr/^No such file or directory/ ) { Err::NotFound->new( handle => $handle )->throw } when ( qr/^Permission denied/ ) { Err::PermissionsDenied->new( handle => $handle )->throw } default { warn $err if $err } } } class FileHandle { has $mode ( is => 'ro' ); has $filename ( is => 'ro' ); has $fh; BUILD ($params) { $fh = IO::File->new( $self->filename, $self->mode ) or convert_error( $!, $self ); } DEMOLISH { $fh->close or convert_error( $!, $self ) if $fh; } } Here is another example of working with older Perl modules, in this case capaturing and converting the errors from IO::File -
use Fun; fun convert_error ($err, $handle) { given ( $err
) { when ( qr/^No such file or directory/ ) { Err::NotFound->new( handle => $handle )->throw } when ( qr/^Permission denied/ ) { Err::PermissionsDenied->new( handle => $handle )->throw } default { warn $err if $err } } } class FileHandle { has $mode ( is => 'ro' ); has $filename ( is => 'ro' ); has $fh; BUILD ($params) { $fh = IO::File->new( $self->filename, $self->mode ) or convert_error( $!, $self ); } DEMOLISH { $fh->close or convert_error( $!, $self ) if $fh; } } Basically this is a utility function, private to the class (presumably this is in a package somewhere). -
use Fun; my fun convert_error ($err, $handle) { given (
$err ) { when ( qr/^No such file or directory/ ) { Err::NotFound->new( handle => $handle )->throw } when ( qr/^Permission denied/ ) { Err::PermissionsDenied->new( handle => $handle )->throw } default { warn $err if $err } } } class FileHandle { has $mode ( is => 'ro' ); has $filename ( is => 'ro' ); has $fh; BUILD ($params) { $fh = IO::File->new( $self->filename, $self->mode ) or convert_error( $!, $self ); } DEMOLISH { $fh->close or convert_error( $!, $self ) if $fh; } } And as soon as the my sub stuff is done, I am sure we can expect this -
use Fun; fun convert_error ($err, $handle) { given ( $err
) { when ( qr/^No such file or directory/ ) { Err::NotFound->new( handle => $handle )->throw } when ( qr/^Permission denied/ ) { Err::PermissionsDenied->new( handle => $handle )->throw } default { warn $err if $err } } } class FileHandle { has $mode ( is => 'ro' ); has $filename ( is => 'ro' ); has $fh; BUILD ($params) { $fh = IO::File->new( $self->filename, $self->mode ) or convert_error( $!, $self ); } DEMOLISH { $fh->close or convert_error( $!, $self ) if $fh; } } So what this function is doing then is converting string errors from IO::File to be exceptions (again note the smart match usage) -
use Devel::StackTrace; role Throwable { has $message ( is =>
'ro' ) = 'Error'; has $stack_trace ( is => 'ro' ) = Devel::StackTrace->new( frame_filter => sub { ... } ); method throw { die $self } method format_message ( $message ) { "$message" } method as_string { $self->format_message( $message ) . "\n" . $stack_trace->as_string } } Here is the actual role those two exceptions came from. Basically this is porting RJBS’s module Throwable (sorta, I simplified it a little for the slide) It nicely shows that it is also possible to use p5-mop and p5 classes together, the simplest is delgation, (though we do want to make inheritance work) -
use Devel::StackTrace; role Throwable { has $message ( is =>
'ro' ) = 'Error'; has $stack_trace ( is => 'ro' ) = Devel::StackTrace->new( frame_filter => sub { ... } ); method throw { die $self } method format_message ( $message ) { "$message" } method as_string { $self->format_message( $message ) . "\n" . $stack_trace->as_string } } here we are using the p5 OO module called Devel::StackTrace -
use Devel::StackTrace; role Throwable { has $message ( is =>
'ro' ) = 'Error'; has $stack_trace ( is => 'ro' ) = Devel::StackTrace->new( frame_filter => sub { ... } ); method throw { die $self } method format_message ( $message ) { "$message" } method as_string { $self->format_message( $message ) . "\n" . $stack_trace->as_string } } here is the throw method, which is not a class method, so you must do ->new->throw. However this is how it working in many popular languages -
use Devel::StackTrace; role Throwable { has $message ( is =>
'ro' ) = 'Error'; has $stack_trace ( is => 'ro' ) = Devel::StackTrace->new( frame_filter => sub { ... } ); method throw { die $self } method format_message ( $message ) { "$message" } method as_string { $self->format_message( $message ) . "\n" . $stack_trace->as_string } } we do not yet have overloading though, so for now you call the as_string method instead -
use Devel::StackTrace; role Throwable (with => [ Printable ]) {
has $message ( is => 'ro' ) = 'Error'; has $stack_trace ( is => 'ro' ) = Devel::StackTrace->new( frame_filter => sub { ... } ); method throw { die $self } method format_message ( $message ) { "$message" } method as_string { $self->format_message( $message ) . "\n" . $stack_trace->as_string } } And you know what, since we have as_string, we might as well make this Printable (our role from the Previous slide) so that people can do this ... -
use Try; try { # ... } catch { when
( Printable ) { warn $_->as_string; } default { warn $_; } } this basically means that code like this is doable ... -
use Try; try { # ... } catch { when
( Printable ) { warn $_->as_string; } default { warn $_; } } Note that this doesn’t say Try::Tiny, it says Try, this is because earlier this week Jesse Luehrs released it. It basically uses the Devel::CallParser module to implement a language level try/catch/finally statement. -
use Try; try { # ... } catch { when
( Printable ) { warn $_->as_string; } default { warn $_; } } this means you have a language level try/catch/finally construct. Currently this is backed by Try::Tiny, but Jesse and Florian are both working on fixing that (as well as making return to do the right thing inside a try). -
use Try; try { # ... } catch { when
( Printable ) { warn $_->as_string; } default { warn $_; } } this means you have a language level try/catch/finally construct. Currently this is backed by Try::Tiny, but Jesse and Florian are both working on fixing that (as well as making return to do the right thing inside a try). -
use Try; try { # ... } catch { when
( Printable ) { warn $_->as_string; } default { warn $_; } } My favorite part though is the use of when in the catch block. Note that this likely wouldn’t work straight away, you will need to do ... -
use Try; try { # ... } catch { when
( $_->does( Printable ) ) { warn $_->as_string; } default { warn $_; } } this, but if the recent smartmatch proposal from RJBS goes through, we should be able to do it -
http://github.com/stevan/p5-mop/ Okay, and thats pretty much about it ... I
want to remind everyone, almost all of these examples (other then the exceptions I mentioned) is actual runnable code. If you go to github you can check it out and try it (and please write tests) -
Moving to Moose Hackathon Oslo.pm Preikestolen, NO 25 ↝ 30
August 2012 ???