2009-05-31

Moose's MOP: Ecosystem

Methods

Previously, we saw that metaclasses hold information about classes. We learned that classes know how they relate to other classes and that they know all of the method names they can accept. In most object-oriented languages that even expose a meta-model, this is as deep as it goes. In CLOS and Moose, the meta-model is much more elaborate. Instead of only metaclasses, we have many distinct kinds of meta-level classes.

$ my $meta = DateTime->meta
Moose::Meta::Class=HASH(0xd84dd4)

$ my $method_metaobject = $meta->get_method('ymd')
Moose::Meta::Method=HASH(0xd3f830)

We have a new meta-level class here, one that represents a method. DateTime's ymd method corresponds to this instance of the Moose::Meta::Method meta-level class. Let's check out what this method metaobject can do. As you've already seen, the way to see what methods $object can accept is $object->meta->get_all_method_names.

$ $method_metaobject->meta->get_all_method_names
$ARRAY1 = [
            'original_fully_qualified_name',
            '_inline_throw_error',
            'dump',
            # ... elided ~16 methods ...
            'execute',
            'original_package_name',
            'fully_qualified_name'
          ];

These are certainly applicable to methods. Of course you can execute a method. fully_qualified_name tells you the full name of the method. original_fully_qualified_name is different when the method came from a role.

You don't normally see this method metaobject. It's just there behind-the-scenes to do bookkeeping on your behalf. The only way to even see this object is if you peer into DateTime->meta to find it. You (and your users) never have to interact with metaobjects if you don't want to. However, I hope to show that you can benefit greatly by interacting with metaobjects.

To be honest, methods aren't particularly interesting because all you can do is invoke them. Perl doesn't have anything like method signatures for you to inspect (unless you use an extension), or generic functions for you multi-dispatch over (again, unless you use an extension). By default, method metaobjects are just glorified functions.

Attributes

Moose's attribute protocol is far more interesting than its method protocol. The metaclass for attributes is just as rich as that of classes.

$ my $attribute_metaobject = $meta->get_attribute('time_zone')
Moose::Meta::Attribute=HASH(0xda709c)

$ $attribute_metaobject->meta->get_all_method_names
$ARRAY1 = [
            'verify_against_type_constraint',
            'is_default_a_coderef',
            '_coerce_and_verify',
            # ... elided ~83 methods ...
            'has_type_constraint',
            'remove_delegation',
            'get_write_method'
          ];

Here we have another new meta-level class, Moose::Meta::Attribute. Given that Moose attributes support type constraints, defaults, type coercion, and delegation, these methods for attributes should come as no surprise. We can invoke some of these methods to, say, get a better idea of how DateTime works.

$ $attribute_metaobject->get_write_method
set_time_zone

$ $attribute_metaobject->has_type_constraint
1

$ $attribute_metaobject->type_constraint
DateTime::TimeZone

$ $attribute_metaobject->_coerce_and_verify('America/New_York')
DateTime::TimeZone::America::New_York=HASH(0xcd3730);

$ $attribute_metaobject->_coerce_and_verify('Europe/New_York')
Runtime error: The timezone 'Europe/New_York' could not be loaded, or is an invalid name.

Perl's builtin OO doesn't really have attributes. At best, programmers write constructors and accessors to interact with each instance's data structure. Moose users have benefitted greatly lot from the reification of attributes. Declarative features like type constraints and lazy defaults improve the robustness and maintainability of code.

Making these declarations inspectable through the metaobject protocol permits even greater reuse. We only have to declare the type constraint of an attribute once to permit validation in both the constructor and all associated accessors. We also open that attribute up for other code (such as MooseX modules, and your own custom code) to make informed decisions about how to interact with that attribute. For example, a web application's form builder can inspect an attribute's type constraint to perform validation of a field through AJAX. As we'll see next time, an ORM could inspect type constraint and other declarations to generate a complete database schema.

There are several other metaclasses in Moose: roles, type constraints, type coercions, instances, and even modules and packages. However, since most inspection (and as we'll see later, extension) happens for attributes and classes, those are where our focus will remain. The principles we cover will apply equally to these other metaclasses.

1 comments:

SamV said...

As we'll see next time, an ORM could inspect type constraint and other declarations to generate a complete database schema.Indeed, I put a few principles for doing this in a paper; see http://samvilain.cgpublisher.com/

IMHO the biggest challenge to doing this correctly is identity keys and foreign keys. An identity key can be achieved with MooseX::NaturalKey, and potentially attributes which are Parametric types of a collection-based type, eg a Set[Type] can be converted into a foreign key. More in the paper - let me know how you find it!

Post a Comment