From: Dave Rolsky Date: Tue, 26 Aug 2008 18:01:08 +0000 (+0000) Subject: Added an overview "recipe" for Moose extensions X-Git-Tag: 0.55_04~2^2~21 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=c8d5f1e1afb0a13f2a0d66b55f7fa550e4b38d1d;p=gitmo%2FMoose.git Added an overview "recipe" for Moose extensions --- diff --git a/lib/Moose/Cookbook.pod b/lib/Moose/Cookbook.pod index 3f338e2..262d488 100644 --- a/lib/Moose/Cookbook.pod +++ b/lib/Moose/Cookbook.pod @@ -169,14 +169,25 @@ if you plan to write your own C module. =over 4 -=item L - Providing an alternate base object class +=item L - Moose extension overview + +There are quite a number of ways to extend Moose. This recipe explains +provides an overview of each method, and provides recommendations for +when each is appropriate. + +=item L - Providing a base object class role + +Many base object class extensions can be implemented as roles. This +example shows how to provide a base object class debugging role. + +=item L - Providing an alternate base object class You may find that you want to provide an alternate base object class along with a meta extension, or maybe you just want to add some functionality to all your classes without typing C over and over. -=item L - Acting like Moose.pm and providing sugar Moose-style +=item L - Acting like Moose.pm and providing sugar Moose-style This recipe shows how to provide a replacement for C. You may want to do this as part of the API for a C module, diff --git a/lib/Moose/Cookbook/Extending/Recipe1.pod b/lib/Moose/Cookbook/Extending/Recipe1.pod new file mode 100644 index 0000000..b849e5a --- /dev/null +++ b/lib/Moose/Cookbook/Extending/Recipe1.pod @@ -0,0 +1,322 @@ + +=pod + +=head1 NAME + +Moose::Cookbook::Extending::Recipe - Moose extension overview + +=head1 DESCRIPTION + +Moose has quite a number of ways in which extensions can hook into +Moose and change its behavior. Moose also has a lot of behavior that +can be changed. This recipe will provide an overview of each extension +method and give you some recommendations on what tools to use. + +If you haven't yet read the recipes on metaclasses, go read those +first. You can't really write Moose extensions without understanding +the metaclasses, and those recipes also demonstrate some basic +extensions mechanisms such as metaclass subclasses and traits. + +=head2 Playing Nice With Others + +One of the goals of this overview is to help you build extensions that +cooperate well with other extensions. This is especially important if +you plan to release your extension to CPAN. + +Moose comes with several modules that exist to help your write +cooperative extensions. These are L and +L. By using these two modules to implement your +extensions, you will ensure that your extension works with both the +Moose core features and any other CPAN extension using those modules. + +=head1 PARTS OF Moose YOU CAN EXTEND + +The types of things you might want to do in Moose extensions broadly +fall into a few categories. + +=head2 Metaclass Extensions + +One way of extending Moose is by extending one or more Moose +metaclasses. For example, in L we saw +a metaclass subclass that added a C attribute to the +metaclass. If you were writing an ORM, this would be a logical +extension. + +Many of the Moose extensions on CPAN work by providing an attribute +metaclass extension. For example, the C +distro provides a new attribute metaclass that lets you delegate +behavior to a non-object attribute (a hashref or simple number). + +A metaclass extension can be packaged as a subclass or a +role/trait. If you can, we recommend using traits instead of +subclasses, since it's generally much easier to combine disparate +traits then it is to combine a bunch of subclasses. + +When your extensions are implemented as roles, you can apply them with +the L module. + +=head2 Providing Sugar Subs + +As part of a metaclass extension, you may also want to provide some +sugar subroutines, much like C does. Moose provides a helper +module called L that makes this much simpler. This +will be used in several of the extension recipes. + +=head2 Object Class Extensions + +Another common Moose extension is to change the default object class +behavior. For example, the C extension changes the +behavior of your objects so that they are singletons. The +C extension makes the constructor reject +arguments which don't match its attributes. + +Object class extensions often also include metaclass extensions. In +particular, if you want your object extension to work when a class is +made immutable, you may need to extend some or all of the +C, C, and +C objects. + +The L module lets you apply roles to the base +object class, as well as the meta classes just mentioned. + +=head2 Providing a Role + +Some extensions come in the form of a role for you to consume. The +C extension is a great example of this. In +fact, despite the C name, it does not actually change anything +about Moose's behavior. Instead, it is just a role that an object +which wants to be pluggable can consume. + +If you are implementing this sort of extension, you don't need to do +anything special. You simply create a role and document that it should +be used via the normal C sugar: + + package RoleConsumer; + + use Moose; + + with 'MooseX::My::Role'; + +=head2 New Types + +Another common Moose extension is a new type for the Moose type +system. In this case, you simply create a type in your module. When +people load your module, the type is created, and they can refer to it +by name after that. The C and +C distros are two good examples of how this +works. + +=head1 ROLES VS TRAITS VS SUBCLASSES + +It is important to understand that B. A role can be used as a trait, and a trait is a role. The only +thing that distinguishes the two is that a trait is packaged in a way +that lets Moose resolve a short name to a class name. In other words, +with a trait, the caller can specify it by a short name like "Big", +and Moose will resolve it to a class like +C. + +See L and +L for examples of traits in action. In +particular, both of these recipes demonstrate the trait resolution +mechanism. + +Implementing an extension as a (set of) metaclass or base object +role(s) will make your extension more cooperative. It is hard for an +end-user to effectively combine together multiple metaclass +subclasses, but it can be very easy to combine roles. + +=head1 USING YOUR EXTENSION + +There are a number of ways in which an extension can be applied. In +some cases you can provide multiple ways of consuming your extension. + +=head2 Extensions as Metaclass Traits + +If your extension is available as a trait, you can ask end users to +simply specify it in a list of traits. Currently, this only works for +metaclass and attribute metaclass traits: + + use Moose -traits => [ 'Big', 'Blue' ]; + + has 'animal' => + ( traits => [ 'Big', 'Blue' ], + ... + ); + +If your extension applies to any other metaclass, or the object base +class, you cannot use the trait mechanism. + +The benefit of the trait mechanism is that is very easy to see where a +trait is applied in the code, and consumers have fine-grained control +over what the trait applies to. This is especially true for attribute +traits, where you can apply the trait to just one attribute in a +class. + +=head2 Extensions as Metaclass (and Base Object) Subclasses + +Moose does not provide any simple APIs for consumers to use a subclass +extension, excep for attribute metaclasses. The attribute declaration +parameters include a C parameter a consumer of your +extension can use to specify your subclass. + +This is one reason why implementing an extension as a subclass can be +a poor choice. However, you can force the use of certain subclasses at +import time by calling C<< Moose->init_meta >> for the caller, and +providing an alternate metaclass or base object class. + +If you do want to do this, you should look at using C +to re-export the C sugar subroutines. When you use +L and your exporting class has an C +method, L makes sure that this C method +gets called when your class is imported. + +Then in your C you can arrange for the caller to use your +subclasses: + + package MooseX::Embiggen; + + use Moose (); + use Moose::Exporter; + + use MooseX::Embiggen::Meta::Class; + use MooseX::Embiggen::Object; + + Moose::Exporter->setup_import_methods( also => 'Moose' ); + + sub init_meta { + shift; # just your package name + my %options = @_; + + return Moose->init_meta( + for_class => $options{for_class}, + metaclass => 'MooseX::Embiggen::Meta::Class', + base_class => 'MooseX::Embiggen::Object', + ); + } + +=head2 Extensions as Metaclass (and Base Object) Roles + +Implementing your extensions as metaclass roles makes your extensions +easy to apply, and cooperative with other metaclass role-based extensions. + +Just as with a subclass, you will probably want to package your +extensions for consumption with a single module that uses +L. However, in this case, you will use +L to apply all of your roles. The advantage of +using this module is that I. This means that your +extension is cooperative I, and consumers of your +extension can easily use it with other role-based extensions. + + package MooseX::Embiggen; + + use Moose (); + use Moose::Exporter; + use Moose::Util::MetaRole; + + use MooseX::Embiggen::Role::Meta::Class; + use MooseX::Embiggen::Role::Meta::Attribute; + use MooseX::Embiggen::Role::Meta::Method::Constructor + use MooseX::Embiggen::Role::Object; + + Moose::Exporter->setup_import_methods( also => 'Moose' ); + + sub init_meta { + shift; # just your package name + my %options = @_; + + Moose->init_meta(%options); + + my $meta = Moose::Util::MetaRole::apply_metaclass_roles( + for_class => $options{for_class}, + metaclass_roles => ['MooseX::Embiggen::Role::Meta::Class'], + attribute_metaclass_roles => + ['MooseX::Embiggen::Role::Meta::Attribute'], + constructor_class_roles => + ['MooseX::Embiggen::Role::Meta::Method::Constructor'], + ); + + Moose::Util::MetaRole::apply_base_class_roles( + for_class => $options{for_class}, + roles => ['MooseX::Embiggen::Role::Object'], + ); + + return $meta; + } + +As you can see from this example, you can use C +to apply roles to any metaclass, as well as the base object class. If +some other extension has already applied its own roles, they will be +preserved when your extension applies its roles, and vice versa. + +=head2 Providing Sugar + +With L, you can also export your own sugar subs, as +well as those from other sugar modules: + + package MooseX::Embiggen; + + use Moose (); + use Moose::Exporter; + + Moose::Exporter->setup_import_methods( + with_caller => ['embiggen'], + also => 'Moose', + ); + + sub init_meta { ... } + + sub embiggen { + my $caller = shift; + $caller->meta()->embiggen(@_); + } + +And then the consumer of your extension can use your C sub: + + package Consumer; + + use MooseX::Embiggen; + + extends 'Thing'; + + embiggen ...; + +This can be combined with metaclass and base class roles quite easily. + +=head1 LEGACY EXTENSION METHODOLOGIES + +Before the existence of L and +L, there were a number of other ways to extend +Moose. In general, these methods were less cooperative, and only +worked well with a single extension. + +These methods include C, C (which uses +C under the hood), and various hacks to do what +L does. Please do not use these for your own +extensions. + +Note that if you write a cooperative extension, it should cooperate +with older extensions, though older extensions generally do not +cooperate with each oether. + +=head1 CONCLUSION + +If you can write your extension as one or more metaclass and base +object roles, please consider doing so. Make sure to read the docs for +L and L as well. + +=head1 AUTHOR + +Dave Rolsky Eautarch@urth.orgE + +=head1 COPYRIGHT AND LICENSE + +Copyright 2006-2008 by Infinity Interactive, Inc. + +L + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=cut diff --git a/lib/Moose/Cookbook/Extending/Recipe3.pod b/lib/Moose/Cookbook/Extending/Recipe3.pod index 47f89f9..3abe740 100644 --- a/lib/Moose/Cookbook/Extending/Recipe3.pod +++ b/lib/Moose/Cookbook/Extending/Recipe3.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Extending::Recipe1 - Providing an alternate base object class +Moose::Cookbook::Extending::Recipe3 - Providing an alternate base object class =head1 SYNOPSIS diff --git a/lib/Moose/Cookbook/Extending/Recipe4.pod b/lib/Moose/Cookbook/Extending/Recipe4.pod index 98a2ea7..a6b9c3f 100644 --- a/lib/Moose/Cookbook/Extending/Recipe4.pod +++ b/lib/Moose/Cookbook/Extending/Recipe4.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Extending::Recipe2 - Acting like Moose.pm and providing sugar Moose-style +Moose::Cookbook::Extending::Recipe4 - Acting like Moose.pm and providing sugar Moose-style =head1 SYNOPSIS