Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Perl 5 MOP

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
Tweet

More Decks by Stevan Little

Other Decks in Programming

Transcript

  1. 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).
  2. 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?
  3. 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
  4. 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.
  5. 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.
  6. “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.
  7. “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.
  8. “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.
  9. “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.
  10. “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.
  11. 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.
  12. 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!
  13. 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.
  14. ... 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.
  15. ... an abstraction of a system of abstractions that is

    used to build abstractions. an abstraction (the MOP)
  16. ... an abstraction of a system of abstractions that is

    used to build abstractions. system of abstractions (classes, methods, etc.)
  17. ... an abstraction of a system of abstractions that is

    used to build abstractions. build abstractions (your classes)
  18. *{$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!
  19. 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
  20. class Point { has $x = 0; has $y =

    0; method clear { ($x, $y) = (0, 0); } }
  21. class Point { has $x = 0; has $y =

    0; method clear { ($x, $y) = (0, 0); } } class keyword - if no superclass, inherits from Object by default
  22. class Point { has $x = 0; has $y =

    0; method clear { ($x, $y) = (0, 0); } } has keyword - "instance-lexical" scoping of attributes
  23. 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
  24. my $point = Point->new( x => 10, y => 20

    ); instance creation is pretty straight forward
  25. my $point = Point->new( x => 10, y => 20

    ); we didn't have to write a &new method, because we inherited from Object
  26. 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)
  27. class Point3D (extends => Point) { has $z = 0;

    method clear { super; $z = 0; } } extend a class
  28. class Point3D (extends => Point) { has $z = 0;

    method clear { super; $z = 0; } } the extends syntax
  29. class Point3D (extends => Point) { has $z = 0;

    method clear { super; $z = 0; } } the super syntax calling the superclass-method
  30. class BankAccount { has $balance ( is => 'ro' )

    = 0; method deposit ($amount) { $balance += $amount } method withdraw ($amount) { die "Account overdrawn" if ($balance <= $amount); $balance -= $amount; } }
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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 ); } }
  36. 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
  37. 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
  38. 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
  39. role Equality { method equal_to; method not_equal_to ($other) { not

    $self->equal_to($other); } } methods with no body are 'required' methods
  40. 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
  41. 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 ...
  42. 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 } }
  43. 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
  44. 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)
  45. class DE::Currency (with => [ Comparable, Printable ]) { has

    $amount ( is => 'rw' )= 0; method compare ($other) { $amount <=> $other->amount } method as_string { sprintf '€ %0.2f' => $amount } }
  46. 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
  47. 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
  48. class Foo { has $bar; method bar { $baz };

    } - spelling errors on slots are compile time errors - similar to inside-out objects, but less cumbersome
  49. 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
  50. my $foobar = Foo::Bar->new( baz => 'gorch' ); ... and

    they are called just as you would expect
  51. 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 ); } }
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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)
  59. 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
  60. use Fun; fun reduce ( $acc, $h, @tail ) {

    return $acc + $h unless @tail; return reduce( $acc + $h, @tail ); }
  61. 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
  62. 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).
  63. 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
  64. 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)
  65. 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)
  66. 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
  67. 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
  68. 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
  69. 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 ...
  70. use Try; try { # ... } catch { when

    ( Printable ) { warn $_->as_string; } default { warn $_; } } this basically means that code like this is doable ...
  71. 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.
  72. 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).
  73. 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).
  74. 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 ...
  75. 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
  76. 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)