October 2012 Archives

Chicago.PM Report - Scripting Git With Perl

This month's presentation was on the Git::Repository Perl module, given by me. In both my jobs, I use the Git::Repository module to automate releases.

At Double Cluepon, I use it to create the release packages based on tagged commits, so that releasing our software is exactly: git tag vX.X.X && git push --tags. A Perl script builds every package and then pushes them to our update server, where the game will check for a new release.

At Bank of America, we use it to combine our 20-30 Perl distributions into a single release. Using git submodules, we have a "release repository" that holds references to all the modules for each team's releases (some are team-specific, others are shared between teams). A Perl script manages the submodules, determines when the submodule refs need to be updated, tags and branches for each release, and finally builds and installs our modules using Module::Build and local::lib.

All this Git stuff gave me some ideas for possible useful code I can release, perhaps leading to me finally recovering my CPAN ID.

The slides for my Scripting Git With Perl talk

The code for the script that automatically builds releases tagged like "vX.X.X"

Using MooseX::Types to inflate config values

For a large application, configuration files become a necessity. They help flexible code be used in multiple instances across multiple modules. But they are, for the most part, only data structures, which can be a problem if the configured object is expecting another configured object.

package FakeRepository;

use Moose;
use TimeSeries;
has timeseries => (
    is => 'rw',
    isa => 'TimeSeries',
    required => 1,
);

package TimeSeries;

use Moose;
has dates => (
    is => 'rw',
    isa => 'HashRef[Number]', # Date => Value pairs
    default => sub { {} },
);

Here we have a FakeRepository that requires a TimeSeries object. Certainly, this is where a Dependency Injection framework could step in and inject the required TimeSeries. The drawback is the indirection: The two configured objects are completely separate, joined only by the reference, like so:

# dependency.yml
- service:
    name: 'test_repo'
    class: 'FakeRepository'
    constructor_args:
        timeseries: { ref: test_data }

- service:
    name: 'test_data'
    class: 'TimeSeries'
    constructor_args:
        dates:
            2012-01-01: 1.56
            2012-01-02: 1.69
            2012-01-03: 1.45

So here, we define two services (objects), test_repo and test_data, and test_repo uses test_data to fill its timeseries attribute. test_data fills in its dates attribute directly from the configuration file.

This works great if test_data is needed by more than just test_repo. It also works fine as-is, the dependency injection framework does the work. But what if we wanted to specify the timeseries value directly, instead of indirectly?

Moose's typing system allows us to do just that. By creating a custom type with a coercion from the data structure in our configuration file, we can create the dependency that our test_repo needs.

package My::Types;

use MooseX::Types qw( HashRef Number );
use TimeSeries;

# declare our TimeSeries class as a type
class_type TimeSeriesType;
# coerce a TimeSeries from a hash of date => value pairs
coerce TimeSeriesType, from HashRef[Number], via sub {
    # coercions put the value to be coerce in $_
    return TimeSeries->new( data => $_ );
};

Once we have our new custom types, we must use them in our package.

package FakeRepository;

use Moose;
use My::Types qw( TimeSeriesType );

has timeseries => qw(
    is => 'rw',
    isa => TimeSeriesType,  # Not quoted!
    coerce => 1,            # Activate coercion powers!
    required => 1,
);

Now, we can configure our TimeSeries directly from our configuration file, without indirection.

# dependency.yml
- service:
    name: 'test_repo'
    class: 'FakeRepository'
    constructor_args:
        timeseries:
            2012-01-01: 1.56
            2012-01-02: 1.69
            2012-01-03: 1.45

Moose will create the object for us using our defined coercion.

There are other ways to solve this: Enhance the Dependency Injection framework to allow anonymous objects (instead of providing a ref: to an object, provide a full object definition with class: and constructor_args:), but having these coercions in place also helps when writing test code:

use Test::More;
use FakeRepository;
my $repo = FakeRepository->new(
    timeseries => {
        '2012-01-01' => 1.56,
        '2012-01-02' => 1.69,
        '2012-01-03' => 1.45,
    },
);

No need to increase the apparent coverage of the test by including the TimeSeries class, we never have to refer to it at all. No need to lock the interface to a specific TimeSeries class (if that's a desired goal of the project), the coercion takes care of creating the actual object used.

Coercions are a powerful feature. I've used them to build complicated trees from arrays of arrays (more on that later), and I've used them to simply force-uppercase a string so that Log4perl would do its job. Coercions are one more very useful tool in a robust toolbox.

Chicago.PM Report - Project Night: Alien::Base

This month's project night focused on Joel Berger's Alien::Base module. The final bugs are either squashed or very close, and we got an introduction to how the whole thing works.

I learned some interesting things about DynaLoader that helped cement the idea that it's an interface on to the system's dynamic linker. I've never dealt with DynaLoader from the Perl side of things, except when it broke, and I would solve those problems in system-specific ways. I learned that DynaLoader could solve those problems in a cross-platform way.

I started writing my own Alien::Base module for libusb, but unfortunately ran out of time. We had some interesting side-discussion on human interface devices and augmented reality.

In two weeks, I'll be giving a talk on scripting Git with Perl. Come on down and check it out!

Run-time Class Composition With Moose

Moose is great! At its very basic, it simplifies the boilerplate required to create Perl objects immensely, providing attributes with type constraints, method modifiers for semantic enhancement, and role-based class composition for better code re-use.

Moose is built on top of Class::MOP. MOP stands for Meta-Object Protocol. A meta-object is an object that describes an object. So, each attribute and method in your class has a corresponding entry in the meta-object describing it. The meta-object is where you can find out what type constraints are on an attribute, or what methods a class has available.

Since the meta-object is a Plain Old Perl Object, we can call methods on it at runtime. Using those meta-object methods to add an attribute would modify our object, adding that attribute to the object. Using Class::MOP, we can compose classes at runtime!

I have recently used this to great effect in a custom dependency injection and configuration framework we have at Bank of America. By adding a "with" key to the configuration YAML file, the DI will create a new, anonymous class that composes the roles specified.

{
    name: "Repository",
    class: "Bank::HistoricalData::DailyRepository",
    with: [ "Bank::Role::FlattenIntraday", "Bank::Role::CalculateHighLow" ],
    constructor_args: { }
}

So, when I ask the DI for the "Repository" object, it will get the meta-object for Bank::HistoricalData::DailyRepository, create an anonymous class that extends Bank::HistoricalData::DailyRepository, and then compose the two roles into the new class.

The code to do all this is extremely short:

my $class = $conf->{class};
my $meta = Moose::Meta::Class->create_anon_class( 
    superclasses => [ $class ],
    roles => $conf->{with},
);
$meta->make_immutable; # for performance
my $object = $meta->name->new( %{ $conf->{constructor_args} } );
return $object;

If a lot of objects end up composing the same roles, I can create a concrete class to get a bit of a performance boost. Since I create a new, anonymous meta-class, I don't have to worry about the class I'm extending being modified, or being made mutable again ($class->meta->make_immutable speeds up a lot of things, but doesn't allow us to add attributes or methods).

With this, I can be a lot more flexible about my dependencies, adding whatever role I want to change their behavior whenever I need.

About preaction

user-pic