The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

MooseX::ComposedBehavior - implement custom strategies for composing units of code

VERSION

version 0.004

OVERVIEW

First, a warning: MooseX::ComposedBehavior is a weird and powerful tool meant to be used only well after traditional means of composition have failed. Almost everything most programs will need can be represented with Moose's normal mechanisms for roles, classes, and method modifiers. MooseX::ComposedBehavior addresses edge cases.

Second, another warning: the API for MooseX::ComposedBehavior is not quite stable, and may yet change. More likely, though, the underlying implementation may change. The current implementation is something of a hack, and should be replaced by a more robust one. When that happens, if your code is not sticking strictly to the MooseX::ComposedBehavior API, you will probably have all kinds of weird problems.

SYNOPSIS

First, you describe your composed behavior, say in the package "TagProvider":

  package TagProvider;
  use strict;

  use MooseX::ComposedBehavior -compose => {
    method_name  => 'tags',
    sugar_name   => 'add_tags',
    context      => 'list',
    compositor   => sub {
      my ($self, $results) = @_;
      return map { @$_ } @$results if wantarray;
    },
  };

Now, any class or role can use TagProvider to declare that it's going to contribute to a collection of tags. Any class that has used TagProvider will have a tags method, named by the method_name argument. When it's called, code registered the class's constituent parts will be called. For example, consider this example:

  {
    package Foo;
    use Moose::Role;
    use TagProvider;
    add_tags { qw(foo baz) };
  }

  {
    package Bar;
    use Moose::Role;
    use t::TagProvider;
    add_tags { qw(bar quux) };
  }

  {
    package Thing;
    use Moose;
    use t::TagProvider;
    with qw(Foo Bar);
    add_tags { qw(bingo) };
  }

Now, when you say:

  my $thing = Thing->new;
  my @tags  = $thing->tags;

...each of the add_tags code blocks above is called. The result of each block is gathered and an arrayref of all the results is passed to the compositor routine. The one we defined above is very simple, and just concatenates all the results together.

@tags will contain, in no particular order: foo, bar, baz, quux, and bingo

Result composition can be much more complex, and the context in which the registered blocks are called can be controlled. The options for composed behavior are described below.

HOW TO USE IT

  1. make a helper module, like the "TagProvider" one above

  2. use the helper in every relevant role or class

  3. write blocks using the "sugar" function

  4. call the method on instances as needed

  5. you're done!

There isn't much to using it beyond knowing how to write the actual behavior compositor (or "helper module") that you want. Helper modules will probably always be very short: package declaration, use strict, MooseX::ComposedBehavior invocation, and nothing more. Everything important goes in the arguments to MooseX::ComposedBehavior's import routine:

  package MyHelper;
  use strict;

  use MooseX::ComposedBehavior -compose => {
    ... important stuff goes here ...
  };

  1;

Options to MooseX::ComposedBehavior

method_name

This is the name of the method that you'll call to get composed results. When this method is called, all the registered behavior is run, the results gathered, and those results passed to the compositor (described below).

sugar_name

This is the of the sugar to export into packages using the helper module. It should be called like this (assuming the sugar_name is add_behavior):

  add_behavior { ...the behavior... ; return $value };

When this block is invoked, it will be passed the invocant (the class or instance) followed by all the arguments passed to the main method -- that is, the method named by method_name.

context

This parameter forces a specific calling context on the registered blocks of behavior. It can be either "scalar" or "list" or may be omitted. The blocks registered by the sugar function will always be called in the given context. If no context is given, they will be called in the same context that the main method was called.

The context option does not affect the context in which the compositor is called. It is always called in the same context as the main method.

Void context is propagated as scalar context. This may change in the future to support void context per se.

compositor

The compositor is a coderef that gets all the results of registered behavior (and also_compose, below) and combines them into a final result, which will be returned from the main method.

It is passed the invocant, followed by an arrayref of block results. The block results are in an undefined order. If the blocks were called in scalar context, each block's result is the returned scalar. If the blocks were called in list context, each block's result is an arrayref containing the returned list.

The compositor is always called in the same context as the main method, even if the behavior blocks were forced into a different context.

also_compose

This parameter is a coderef or method name, or an arrayref of coderefs and/or method names. These will be called along with the rest of the registered behavior, in the same context, and their results will be composed like any other results. It would be possible to simply write this:

  add_behavior {
    my $self = shift;
    $self->some_method;
  };

...but if this was somehow composed more than once (by repeating a role application, for example) you would get the results of some_method more than once. By putting the method into the also_compose option, you are guaranteed that it will run only once.

method_order

By default, registered behaviors are called on the most derived class and its roles, first. That is: the class closest to the class of the method invocant, then upward toward superclasses. This is how the DEMOLISH methods in Moose::Object work.

If method_order is provided, and is "reverse" then the methods are called in reverse order: base class first, followed by derived classes. This is how the BUILD methods in Moose::Object work.

AUTHOR

Ricardo Signes <rjbs@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by Ricardo Signes.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.