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

Extending Moose for Applications

Extending Moose for Applications

Using Moose provides many immediate and obvious benefits, starting with the obviation of typing "use strict" and "use warnings" in your classes.

The real power of Moose, however, rests in its extensibility. By subclassing Moose's metaclasses, you can augment and change Moose's behavior to suit your application's needs. This extensibility is powered by the meta-object protocol of Moose's foundation, Class::MOP.

The motivating example for extending Moose will be the creation of a small web framework to empower a Twitter-alike. The focus will be creating meta-level roles so that metaclasses may select exactly which changes in behavior they wish to exhibit. Modules that will be used include Moose::Exporter (to define sugary keywords) and Moose::Util::MetaRole (to extend classes composably).

Experience with using Moose to create regular classes is expected. Having some familiarity with roles will let you get more out of the talk. No experience with metaprogramming is required.

Shawn Moore

October 23, 2011
Tweet

More Decks by Shawn Moore

Other Decks in Programming

Transcript

  1. Extending Moose for Applications Shawn M Moore 1 Best Practical

    Solutions 1 Tuesday, June 23, 2009 Presented YAPC::NA, 2009-06-23. CMU, Pittsburgh, PA.
  2. Twitter! @sartak Hey wait, what is "meta"? 2 2 Tuesday,

    June 23, 2009 People can be shy about asking questions in a room full of strangers. So if you want, you can ask me a question on Twitter and I'll eventually get to it. Just direct your questions at @sartak. That way you don't forget it or worry about interrupting me. Got the idea from Giles Bowkett: http:// gilesbowkett.blogspot.com/2009/04/how-to-take-questions-at-tech.html
  3. (or IRC) 3 3 Tuesday, June 23, 2009 Of course,

    many people dislike Twitter, so I'll check out at IRC as a last resort. I'm Sartak there too. You don't have to use either of these. If you have a burning question or correction, shoot your hand up at any time.
  4. Extending Moose for Applications 4 4 Tuesday, June 23, 2009

    The title of this talk is Extending Moose for Applications. It means just what it says. We want to extend Moose to be more useful and expressive for the problem at hand.
  5. 5 Domain-specific Metaprogramming 5 Tuesday, June 23, 2009 But I

    really wanted to call the talk Domain-specific Metaprogramming. It's more honest. But it's pretentious and a little scary. Hell, with that title I would have been able to sleep in today.
  6. Extending Moose for Applications 7 Domain-specific Metaprogramming 7 Tuesday, June

    23, 2009 You extend Moose through metaprogramming, which is what the talk is all about.
  7. Extending Moose for Applications 8 Domain-specific Metaprogramming 8 Tuesday, June

    23, 2009 And an application is a domain-specific system. I want to teach you new concepts and techniques so that you can solve your business needs.
  8. 9 9 Tuesday, June 23, 2009 In March I was

    at a Lisp conference with jrockway. I'm not much of a Lisper but I do appreciate Lisp for the huge impact it has had on language design. The conference was very enjoyable. What stuck out most was CLOS, which is Common Lisp's version of Moose. It is 15 years older.
  9. 10 Context-oriented Programming with ContextL Towards a Secure Programming Language:

    An Access Control System for CommonLisp Rhapsody: How CLOS simplifies System-in-a-Package Design 10 Tuesday, June 23, 2009 When I was there, quite a few talks began with "I extended CLOS to do X". I was so impressed that immediately after the first day of the conference, I submitted this talk for YAPC. I think Moose is ready to let many more people seriously dig into its guts to make their applications much better.
  10. Metaprogramming 11 11 Tuesday, June 23, 2009 What do I

    actually mean by "metaprogramming"?
  11. Metaprogramming 12 $obj->can(‘method_name’) 12 Tuesday, June 23, 2009 How many

    of you have asked an object if it performs a particular method?
  12. Metaprogramming 14 $obj->can(‘method_name’) $obj->isa(‘Class::Name’) $obj->DOES(‘RoleName’) 14 Tuesday, June 23, 2009

    Or if an object does a particular role? (chromatic?) This is a relatively new one, added in 5.10.
  13. Metaprogramming 15 $obj->can(‘method_name’) $obj->isa(‘Class::Name’) $obj->DOES(‘RoleName’) eval “code” 15 Tuesday, June

    23, 2009 Who here has programmatically generated code? String eval has a lot of well-deserved stigma, but it's occasionally necessary.
  14. Metaprogramming 16 my $code = setup(); $code .= important_stuff(); $code

    .= teardown(); eval $code; 16 Tuesday, June 23, 2009 String eval is interesting because there's an obvious separation of the code that is doing the generating (all these function calls and concatenation) and the code that is generated (the contents of $code). We could say that the first three lines are metaprogramming, since it's code whose domain is other code. The contents of $code would be the "base" level.
  15. Metaprogramming 17 __PACKAGE__->meta->make_immutable 17 Tuesday, June 23, 2009 Here's another

    example of metaprogramming. Many of you have cargo culted this for your Moose classes to make them faster. (Somehow!) Have you ever stopped to just look at this? Obviously the whole expression means to make the current class immutable.
  16. Metaprogramming 18 __PACKAGE__->meta->make_immutable 18 Tuesday, June 23, 2009 There are

    two method calls. What is that first one actually doing?
  17. Moose::Meta::Class 21 name version attributes methods superclasses roles attribute_metaclass method_metaclass

    constructor_name constructor_class new_object clone_object rebless_instance subclasses linearized_isa add_attribute has_method get_all_method_names is_immutable calculate_all_roles Attributes Methods 21 Tuesday, June 23, 2009 Moose::Meta::Class is a class *for* classes. Like every ordinary class, Moose::Meta::Class has attributes and methods. An instance of Moose::Meta::Class *describes* some class. A class has a name, a list of superclasses, a list of methods. You can also create a new_object of a class, or add an attribute to a class, or get the complete list of roles the class does.
  18. Metaprogramming 22 my $code = setup(); $code .= important_stuff(); $code

    .= teardown(); eval $code; 22 Tuesday, June 23, 2009 If we go back to the string eval example, we can see some parallels. Moose itself is like the first section, which is the meta level. Your class is similar to the contents of $code, which is the base level. In Moose, there is far more interaction between the two layers than in this example.
  19. package Point; use Moose; has ‘x’ => (is => ‘rw’,

    isa => ‘Int’); has ‘y’ => (is => ‘rw’, isa => ‘Int’); sub clear { ... } 23 23 Tuesday, June 23, 2009 This is the Point class again, but with Moose. Before I can talk about *extending* Moose, you have to know a bit about how it works internally.
  20. Point->meta my $point = Point->meta; 24 24 Tuesday, June 23,

    2009 We now have the class metaobject for Point in $point. We could call make_immutable on it if we wanted to make Point faster. But instead, let's inspect it.
  21. Point->meta my $point = Point->meta; $point->name # ? 25 25

    Tuesday, June 23, 2009 If we look at the name of the class metaobject, we'll get...
  22. Point->meta my $point = Point->meta; $point->name # Point 26 26

    Tuesday, June 23, 2009 Point, which is the name of the class that $point describes.
  23. Point->meta my $point = Point->meta; $point->name # Point $point->get_attribute_list #

    ? 27 27 Tuesday, June 23, 2009 And the list of attributes?
  24. Point->meta my $point = Point->meta; $point->name # Point $point->get_attribute_list #

    y, x 28 28 Tuesday, June 23, 2009 y and x. You might've expected x and y, since that was the order we added them to the class. However, the attribute and method lists are unordered. The canonical representation of these lists in Moose is a hash. The list methods just perform "keys" on that hash. This way we get free name collision resolution.
  25. Point->meta my $point = Point->meta; $point->name # Point $point->get_attribute_list #

    y, x $point->has_method(‘clear’) # ? 29 29 Tuesday, June 23, 2009 And finally does Point have a clear method?
  26. Point->meta my $point = Point->meta; $point->name # Point $point->get_attribute_list #

    y, x $point->has_method(‘clear’) # 1 30 30 Tuesday, June 23, 2009 Yes.
  27. package Point3D; use Moose; extends ‘Point’; has ‘z’ => (is

    => ‘rw’, isa => ‘Int’); after clear => sub { ... }; 31 31 Tuesday, June 23, 2009 And next up we have the Point3D subclass of Point. It has an additional attribute 'z' and a method modifier on the 'clear' method.
  28. Point3D->meta my $point3d = Point3D->meta; 32 32 Tuesday, June 23,

    2009 Let's start by asking the same questions.
  29. Point3D->meta my $point3d = Point3D->meta; $point3d->name # Point3D $point3d->get_attribute_list #

    ? 35 35 Tuesday, June 23, 2009 What attributes does Point3D have?
  30. Point3D->meta my $point3d = Point3D->meta; $point3d->name # Point3D $point3d->get_attribute_list #

    z 36 36 Tuesday, June 23, 2009 Just one, z! get_attribute_list works based on the *local* class. It does not consider inheritance. When you're using metaclasses you need to know whether inherited entities are included or ignored.
  31. Point3D->meta my $point3d = Point3D->meta; $point3d->name # Point3D $point3d->get_attribute_list #

    z $point3d->has_method(‘clear’) # ? 37 37 Tuesday, June 23, 2009 Clear method?
  32. Point3D->meta my $point3d = Point3D->meta; $point3d->name # Point3D $point3d->get_attribute_list #

    z $point3d->has_method(‘clear’) # 1 38 38 Tuesday, June 23, 2009 Yes, Point3D does have a clear method.
  33. package Point3D; use Moose; extends ‘Point’; has ‘z’ => (is

    => ‘rw’, isa => ‘Int’); after clear => sub { ... }; 39 39 Tuesday, June 23, 2009 Even though in this case "after clear" is just a method modifier, that is good enough for has_method. has_method, like has_attribute, checks only the local class, it ignores inherited methods.
  34. Finding Things 40 has_attribute has_method superclasses find_attribute_by_name find_method_by_name class_precedence_list Local

    Global get_method_list get_attribute_list get_all_attributes get_all_methods 40 Tuesday, June 23, 2009 For many metaclass methods, you have to know whether inheritance is considered. Generally, methods that ignore inheritance have shorter names than methods that include inheritance. The word "all" is a good indicator as well. You also need to know whether the method returns names or objects, though that's less easy to screw up. When you run your code the first time, the wrong choice will be immediately evident.
  35. REST Interface 41 my $class = url2class($url); my $meta =

    $class->meta; for ($meta->get_all_attributes) { my $name = $_->name; my $tc = $_->type_constraint; my $default = $_->default; if ($_->is_required) { ... } } 41 Tuesday, June 23, 2009 One of the uses of this sort of introspection could be a REST interface. In addition to the usual CRUD operations, you could dump the class to YAML or JSON as documentation. Another program could use that description to generate classes. You have all this data available when you write classes with Moose, so reusing it in your application is far better than declaring it several times. Don't repeat yourself.
  36. 42 use Moose; 42 Tuesday, June 23, 2009 Don't get

    the wrong idea about this slide! I'm going to define the Point class again, but without actually using the Moose sugar. I want to demonstrate that metaclasses do more than just describe classes, we can change them too. And Moose users do that all the time when they use the friendly functions like "has"
  37. my $point = Moose::Meta::Class->create( ‘Point’, ); 43 43 Tuesday, June

    23, 2009 We start by creating the metaclass. Note that the name is the first parameter with no hash key. This interface sucks, we know. :(
  38. my $point = Moose::Meta::Class->create( ‘Point’, ); $point->superclasses(‘Moose::Object’); 44 44 Tuesday,

    June 23, 2009 Then we set the superclass. If we don't do this then we'll get no "new" method. This is one of the things "use Moose" does transparently for us.
  39. my $point = Moose::Meta::Class->create( ‘Point’, ); $point->superclasses(‘Moose::Object’); $point->add_attribute( ‘x’, is

    => ‘ro’, isa => ‘Int’, ); 45 45 Tuesday, June 23, 2009 The "x" attribute. Again we pass the name without a key, but the other options are the same.
  40. … $point->add_attribute( ‘y’, is => ‘ro’, isa => ‘Int’, );

    46 46 Tuesday, June 23, 2009 The "y" attribute. It's the same as x but for the name, so maybe a loop would be better.
  41. … $point->add_attribute( ‘y’, is => ‘ro’, isa => ‘Int’, );

    $point->add_method(clear => sub { ... }); 47 47 Tuesday, June 23, 2009 And finally the clear method. All of Moose's sugar functions are thin wrappers for metaclass methods. "has" and its friends actually form a very small part of Moose.
  42. Ecosystem 48 Classes Attributes Methods Roles Type Constraint Type Coercion

    48 Tuesday, June 23, 2009 These are all the important entities that Moose provides.
  43. Ecosystem 49 Classes Attributes Methods Roles Type Constraint Type Coercion

    Moose::Meta::Class 49 Tuesday, June 23, 2009 We've seen that Moose::Meta::Class objects each describe a class.
  44. Ecosystem 50 Classes Attributes Methods Roles Type Constraint Type Coercion

    Moose::Meta::Class Moose::Meta::Attribute Moose::Meta::Method Moose::Meta::Role Moose::Meta::TypeConstraint Moose::Meta::TypeCoercion 50 Tuesday, June 23, 2009 We also have classes for all the other interesting things in the program. Moose's devs like OO, so it makes sense that Moose itself is designed using object-oriented programming.
  45. Ecosystem 51 my $x = Point->meta->get_attribute(‘x’) $x->name # x $x->get_read_method

    # x $x->type_constraint # Int 51 Tuesday, June 23, 2009 We can get the attribute metaobject with the "get_attribute" method. get_read_method returns the name of a method that can be used to read the attribute's value. We call the method it returns ("x") on an instance of Point.
  46. Ecosystem 52 my $clear = Point->meta->get_method(‘clear’) $clear->name # clear $clear->package_name

    # Point 52 Tuesday, June 23, 2009 There are also method metaobjects. There's not much to them though. All you can really do with methods is invoke them.
  47. Extending 1. Extend a metaclass 2. Use it 53 Tuesday,

    June 23, 2009 There are two steps to extend Moose. You extend a particular metaclass with subclassing or role application. Then you just use it. We definitely prefer role application over subclassing. That lets extensions compose. If everything subclassed, then you'd only be able to use one extension at a time.
  48. Extending Class that counts its instances 54 Tuesday, June 23,

    2009 We want to create a class that counts the number of instantiations.
  49. package HasCounter; use Moose::Role; has count => ( is =>

    ‘rw’, isa => ‘Int’, default => 0, ); sub increment { my $self = shift; $self->count( $self->count + 1 ); } 55 Tuesday, June 23, 2009 We start by defining a role that has a counter attribute and a method to increment it. This is a role that you could consume in your ordinary classes. It's not special in any way.
  50. package CountInstances; use Moose::Role; with ‘HasCounter’; after new_object => sub

    { my $self = shift; $self->increment; }; 56 Tuesday, June 23, 2009 Now we define a new role that we expect to apply to Moose::Meta::Class that hooks up the counter role to the "new_object" method.
  51. package Point; use Moose -traits => [ ‘CountInstances’, ]; has

    x => ( is => ‘ro’, isa => ‘Int’, ); ... 57 Tuesday, June 23, 2009 Now all we have to do use tell Moose we want to use this role on the metaclass. After the special - traits option to use Moose, the rest is just the same. The words "trait" and "role" are mostly synonymous. There are vague differences but not worth covering. When you're adding roles to metaclasses in Moose, they're called traits.
  52. Point->meta->count # 0 Point->new Point->meta->count # 1 Point->new for 1

    .. 5 Point->meta->count # 6 58 Tuesday, June 23, 2009 We can inspect the count which is an attribute of "Point->meta".
  53. Point->new Point->meta->new_object after new_object Point->meta->increment Base Meta 59 Tuesday, June

    23, 2009 Point->new is a Moose::Object method that enters the meta-level, hence the red arrow. The black arrows indicate method calls that don't traverse the base/meta boundary.
  54. package Line; use Moose; has start => ( is =>

    ‘ro’, isa => ‘Point’, ); has end => ( is => ‘ro’, isa => ‘Point’, ); 60 Tuesday, June 23, 2009 I want to introduce another class, Line. This does NOT use the CountInstances trait.
  55. Line->new Line->meta->new_object Base Meta 61 Tuesday, June 23, 2009 The

    Line class's instances are not counted. The major point is that one class's extensions do not affect other classes. We're not monkeypatching in a method modifier or attribute or anything like that. With monkeypatching you could destroy Moose's own workings by redefining a particular method it uses internally. This is Perl, we love to design modular, robust systems. Monkeypatching is the antithesis of that ideal.
  56. package HasCounter; use Moose::Role; use MooseX::AttributeHelpers; has count => (

    traits => [‘Counter’], provides => { inc => ‘increment’, }, ); 62 Tuesday, June 23, 2009 We can also apply roles to other metaclasses, such as attributes. Many of you have used MooseX::AttributeHelpers; it works by extending the attribute metaobject. "provides" is an attribute of the AttributeHelpers Counter role which ultimately uses "add_method" on your class.
  57. package FieldType; use Moose::Role; use Moose::Util::TypeConstraints; has render_as => (

    is => ‘ro’, isa => (enum [ ‘text’, ‘textarea’, ‘password’, ..., ]), ); 63 Tuesday, June 23, 2009 We're writing a web application so it's important to know how to render every attribute as a form field. We'll use a role to let the application writer (usually you!) declare what type of <input> element each attribute uses.
  58. package User; use Moose; has name => ( traits =>

    [‘FieldType’], is => ‘rw’, isa => ‘Str’, render_as => ‘text’, ); 64 Tuesday, June 23, 2009 We're writing a user class. The name is usually a short string of characters, so a regular text element makes sense for it.
  59. ... has password => ( traits => [‘FieldType’], is =>

    ‘rw’, isa => ‘Str’, render_as => ‘password’, ); 65 Tuesday, June 23, 2009 For a password, we want to obscure the value of the element so that someone looking over your shoulder can't steal your password.
  60. ... has biography => ( traits => [‘FieldType’], is =>

    ‘rw’, isa => ‘Str’, render_as => ‘textarea’, ); 66 Tuesday, June 23, 2009 And finally we let the user explain all about themselves in a long biography textarea.
  61. traits => [‘FieldType’], render_as => ‘text’, traits => [‘FieldType’], render_as

    => ‘password’, traits => [‘FieldType’], render_as => ‘textarea’, 67 Tuesday, June 23, 2009 That's a lot of redundancy. We know that when we're writing a web application, most classes will eventually be rendered as a form some day.
  62. traits => [‘FieldType’], render_as => ‘text’, traits => [‘FieldType’], render_as

    => ‘password’, traits => [‘FieldType’], render_as => ‘textarea’, 68 Tuesday, June 23, 2009 Ideally we'd be able to say just how each field is rendered, not that it is going to have a particular field type. This problem is more pronounced when you are using many metaclass extensions together.
  63. Moose::Exporter Moose::Util::MetaRole 69 Tuesday, June 23, 2009 We have a

    solution that comes in two modules. These modules are the workhorses of extending Moose. They were written by Dave Rolsky after he realized that Moose extensions were not as composable as we wished. These modules let you do the right thing easily, which is the goal of every well-designed module.
  64. Moose::Exporter Moose::Util::MetaRole 70 Tuesday, June 23, 2009 Moose::Exporter is a

    module that lets you write modules like "Moose" and "Moose::Role". These modules create a class (or role) metaobject, and provide for the user some sugar functions. Moose and Moose::Role are themselves implemented with Moose::Exporter.
  65. Moose::Exporter Moose::Util::MetaRole 71 Tuesday, June 23, 2009 MetaRole is a

    module that lets you apply roles to the various metaclasses in your class. You use it inside of Moose::Exporter.
  66. package MyWeb::OO; use Moose (); use Moose::Exporter; use Moose::Util::MetaRole; use

    FieldType; Moose::Exporter->setup_import_methods( also => ‘Moose’, ); sub init_meta { ... } 72 Tuesday, June 23, 2009 Here we're defining a module MyWeb::OO that people use instead of Moose itself. We have to load a bunch of stuff, including the role we're going to apply to every attribute. We then setup import methods. This particular invocation makes MyWeb::OO provide all of the Moose sugar. You could add new functions to that if you wanted to.
  67. my $class = shift; my %options = @_; Moose->init_meta(%options); Moose::Util::MetaRole::apply_metaclass_roles(

    for_class => $options{for_class}, attribute_metaclass_roles => [‘FieldType’], ); return $options{for_class}->meta; 73 Tuesday, June 23, 2009 Here is the good stuff, inside the init_meta method. This is called to construct a metaclass for the user of MyWeb::OO. We let Moose's own init_meta do the heavy lifting. We then change it slightly so that FieldType is automatically applied to attribute metaclasses. This is a pretty decent amount of code, but it's all well documented and could be abstracted further if you wanted to make this common case require less typing.
  68. package User; use MyWeb::OO; has name => ( is =>

    ‘rw’, isa => ‘Str’, field_type => ‘text’, ); ... 74 Tuesday, June 23, 2009 Let's revisit the User class now that we have this shiny new exporter. We've extended the attribute in a useful way. The user doesn't need to know the traits invocation. They don't need to know about all this metaprogramming stuff. They just use this new option to "has" as if it were in Moose from the start.
  69. package User; use MyWeb::OO; use MyWeb::OO::Persistent; use MyWeb::OO::RESTful; use MyWeb::OO::IncludeInAdminUI;

    use MyWeb::OO::SpamTarget; database_name ‘person’; # legacy has email => ( is => ‘rw’, isa => ‘Str’, field_type => ‘text’, spam_this => 1, admin_editable => 0, primary_key => 1, ); 75 Tuesday, June 23, 2009 Another important point of Moose::Util::MetaRole is that it composes with other extensions seamlessly. You could write all of these Moose extensions that inject all sorts of roles into the various metaclasses. For example, Persistent would inject both class and attribute roles, and provide a database_name keyword.
  70. Immutability 76 Tuesday, June 23, 2009 Though it served as

    a useful launching-off point, immutability is the most irritating thing about extending Moose. If your extensions affect object construction or accessors, then you will probably need to care about immutability.
  71. sub _initialize_body { my $self = shift; my $source =

    'sub {'; $source .= "\n" . 'my $class = shift;'; $source .= "\n" . 'return $class->Moose::Object::new(@_)'; $source .= "\n if \$class ne '" . $self->associated_metaclass->name . "';\n"; $source .= $self->_generate_params('$params', '$class'); $source .= $self->_generate_instance('$instance', '$class'); $source .= $self->_generate_slot_initializers; $source .= $self->_generate_triggers(); $source .= ";\n" . $self->_generate_BUILDALL(); $source .= ";\nreturn \$instance"; $source .= ";\n" . '}'; ... 77 Tuesday, June 23, 2009 One of the ways we make Moose faster is by string evaling constructors and accessors. That certainly makes Moose faster, but for the .1% of users who want to extend Moose, it sucks. You need to hook the methods called here to add the string of code that you need. You can also turn off immutabilization, but that slows the class down. Damned if you do, damned if you don't.
  72. KiokuDB Fey::ORM 78 Tuesday, June 23, 2009 These are some

    projects that extend Moose to great effect. If I've convinced you that domain- specific metaprogramming is awesome, you should read through their source.
  73. KiokuDB Fey::ORM ContextL (CLOS) 79 Tuesday, June 23, 2009 I'd

    also like to give a shout-out to Pascal Costanza's ContextL project. This is a pretty serious extension of CLOS's metaobject protocol. It provides things like layered classes where layers are dynamically turned on and off which alters the class definition. It's really neat and worth looking at.
  74. ROLES! 80 Tuesday, June 23, 2009 Roles themselves would not

    have been easy or clean without Class::MOP's metaobject protocol.
  75. 81 Tuesday, June 23, 2009 Finally, if you really like

    this stuff, get this book. Alan Kay, inventor of OO, said "This is the best book anybody has written in ten years". He also says it's a very hard book to read because of its Lisp-centric nature, but hopefully that isn't too big a stumbling block. It's just an abstract syntax tree!