Just A Summary

Piers Cawley Practices Punditry

Class decomposition and a handy delegation pattern 3

Posted by Piers Cawley Wed, 25 Nov 2009 22:49:00 GMT

There’s something satisfying about reaching the point when you can’t decompose an object any further and all your methods are tiny and do one thing – it’s especially gratifying when you learn something new in the process. Sadly, it doesn’t happen as often as I’d like, there’s usually annoying bits and pieces where you have to placate the language in some fashion that breaks the flow of what you’re writing.

As I get a better handle on the way MooseX::Declare has changed Perl, I’m finding I have to do much less in the way of placation.

Here’s an example. For context, I’m writing a traffic shaping tool. The basic client interface needs to look something like:

$policy->current_weight # => a percentage between 0 and 100

Not much of an interface really. The requirements state that we should be able to specify weights with 15 minute granularity for every day of the week. Our problem becomes one of mapping from a time to a number between 0 and 7 (days) * 24 (hours) * 4 (quarters) - 1 and looking up the weight in an array.

Here’s my first cut:

use MooseX::Declare;
class WeightVector {
    use DateTime;

    has vector => (
        isa => 'Array[Int]',
        is => 'ro',
        required => 1,
    );

    method current_weight {
        my $now = DateTime->now;
        my $offset = ($now->wday_0 * 7 * 24 + $now->hours) * 4 + int($now->minutes / 15);
        return $self->vector->[$offset];
    }
}

Which is, I suppose, perfectly respectable. However, current_weight isn’t filling me with delight. First it finds the current time, then it converts the time into an offset, then it uses the offset to lookup the weight in the vector. Let’s introduce a method to find the weight at a specific time1, the relevant code becomes:

method current_weight {
    $self->weight_at(DateTime->now);
}

method weight_at (DateTime $time) {
    my $offset = ($now->wday_0 * 7 * 24 + $now->hours) * 4 + int($now->minutes / 15);
    return $self->vector->[$offset];
}

And again, we could rest here, but again, we’re doing two things. We’re converting from a time to an offset, then we’re looking up the value in the vector. Type conversions tend to happen again and again, so it’s good if we can specify them separately. We could write a time_to_offset helper method, but we’re in Mooseland now; there’s a better way. Let’s introduce a formal Moose type and define a coercion for it. Here’s the type definition stanza of the code. I’ve taken the opportunity to add types which do bounds checking for the vector as well, while I’m about it.2

use MooseX::Declare;
class WeightVector {
    use DateTime;
    use Moose::Autobox;
    use MooseX::Types -declare => [qw(SlotOffset VectorOfWeights PercentageInt)];
    use MooseX::Types::Moose qw(ArrayRef Int);

    use constant TOTAL_SLOTS = 7 * 24 * 4;

    BEGIN {
        subtype PercentageInt,
            as Int,
            where { 0 <= $_ && $_ <= 100 },
            message { "$_ does not is not an integeter between 0 and 100" };

        subtype VectorOfWeights,
            as ArrayRef[PercentageInt],
            where { $_->length == TOTAL_SLOTS }
            message { "Vector must have ".TOTAL_SLOTS." entries, not ".$_->length };

        subtype SlotOffset,
            as Int,
            where { 0 <= $_ && $_ < TOTAL_SLOTS };

        class_type 'DateTime';

        coerce SlotOffset,
            from 'DateTime',
            via { ($_->wday_0 * 7 * 24 + $_->hours) * 4 + int($_->minutes / 15) };

        # Let's allow clients not to care about using DateTime by allowing 
        # them to simply pass the results of calling 'time()' - It's not like it's
        # still 1970...

        coerce SlotOffset
            from subtype(as => Int, where { $_ > TOTAL_SLOTS }
            via { to_SlotOffset(DateTime->from_epoch(epoch => $_) };
    }

    has vector => (
        is => 'ro',
        isa => VectorOfWeights,
        required => 1,
    }

    ...

Now, if we were programming in plain old Moose, we could rewrite weight_at like so:

sub weight_at {
    my $self = shift;
    my $offset = to_SlotOffset(shift);
    $self->vector->[$offset]
}

Which would be pretty sweet, but we’re using MooseX::Declare; there’s an even better way:

method weight_at (SlotOffset $offset does coerce) {
    $self->vector->[$offset];
}

Sweet!

We could stop there, but I had an insight. What we’ve got here is basically a wrapper around a delegation to our vector, and Moose’s new native types feature let us express the delegation to the vector quite neatly, like so:

has vector => (
    isa => 'VectorOfWeights',
    is => 'ro',
    required => 1,
    traits => ['Array'],
    handles {
        weight_at => 'get',
    },
);

...

around weight_at (SlotOffset $offset does coerce) {
    $self->$orig($offset);
}

This could be overkill when vector is a simple ArrayRef as we have here, but the pattern of delegating declaratively in the attribute definition and then munging arguments in an around handler is applicable to more than just argument transformation. A typical delegation pattern involves having the delegating object passing itself in as an argument to the method delegated to. The nature of Moose’s handles declarations makes that impossible to do within the attribute declaration, but it’s easy to fix with an around helper:

around delegated_method (Any @args) {
    $self->$orig($self, @args);
}

(If you’re wrapping more than one method in this fashion, you should probably consider using a plain old Moose style around handler, which lets you wrap multiple methods with around @delegated_methods => sub {...}

So, at the end of all that, and after we’ve extracted our Type declarations into WeightVector::Types, we have:

use MooseX::Declare;
class WeightVector {
    use WeightVector::Types qw(VectorOfWeights PercentageInt SlotOffset);

    has vector => (
        isa => 'VectorOfWeights',
        is => 'ro',
        required => 1,
        traits => ['Array'],
        handles {
            weight_at => 'get',
        },
    );

    method current_weight {
        $self->weight_at(time());
    }

    around weight_at (SlotOffset $offset does coerce) {
        $self->$orig($offset);
    }
}

And we’ve pushed all knowledge of DateTime off onto our type declarations and gained a boatload of handy bounds checking. We’ve also got a new tool for handling tricky delegation setups in the handles/around combo.

Notes

Motivation

I realise that this looks like a radical decomposition of the class with very little motivation, but it was driven by tests and by some other requirements that I’ve removed from the body of the post. In particular, the type coercions were driven by the need to build particular vectors for testing, a key method being:

method set_weight (PercentageInt $weight, 
                   SlotOffset $from does coerce,
                   SlotOffset $to does coerce)
{
    ...
}

Type coercion is wonderful

Generally, I’m not a fan of static typing. I’m from the “duck type all the way” school of programming,3 so most of my method declarations have no type declarations. But type declarations, especially ones that coerce, make so much sense on methods that make up the public protocol of a class. I only use type declarations on internal methods when I need a narrower coercion, or if I’m using MooseX::Multimethods, which I still haven’t used for anything but exploration.

Updates

Thanks to Chris Dolan for spotting that I’d got the SlotOffset coercion completely wrong. The real code’s doing the right thing, but that’s what comes of recreating code from memory.

1 This was actually motivated by trying to write tests to verify that the weights were correctly set.

2 I’m declaring these in a BEGIN block of the class itself mostly for explanatory purposes – there’s a good case for moving them out into a separate file and pulling it in with use.

3 Except during my periodic attempts to learn Haskell. I’ve learned Haskell at least three times now.

Twice now 4

Posted by Piers Cawley Wed, 25 Nov 2009 22:49:00 GMT

In Ruby, when you’re doing division on integers, things can get a little counter intuitive. For instance, 6/4 is obviously 1. At least, it is until you decide that you’d rather have numbers that behave a little more like ‘real’ numbers and you do require 'mathn', a module in the Ruby standard library (ie, it comes as part of ruby). Then you enter a whole new world of rational maths, where 6 / 4 returns 3/2.

Several very fine and useful Ruby gems rely on the workings of mathn, including ruby-units, which is a spiffy tool for avoiding problems when one team is working in kilometres and the other in miles and it’s no fun at all when your space probe is suddenly incommunicado.

Other fine and dandy Ruby gems include ultrasphinx and webrat. Both of these two (and no doubt others) rely on the the fact that 302/100 == 3.

Hmm… can you see my problem?

Please, if you’re working on a gem that you intend to publish widely, then adopt the practice of never trusting that dividing an integer by another integer will return a third integer. You’re not even making yourself a hostage to some other gem, you’re making yourself a hostage to the standard library. Always do (an_integer / another).to_i and your code will be so much more robust.

I’ve got a pull request and lighthouse ticket in for webrat and, once I’ve hit ‘publish’ on this post, I shall be doing the same thing for ultrasphinx, but I’m sure there are other gems out there with the same problems. Please people, check your assumptions.

Test::Class::Sugar 0.3, no, 0.4

Posted by Piers Cawley Thu, 05 Nov 2009 08:12:00 GMT

tap tap... Is this thing on?

So, I recently noticed that Test::Class 0.33 got released, which means that Test::Class::Sugar no longer needs to depend on a development release, and I also noticed that it was embarrassingly easy to throw Test::Class::Sugar into an infinite loop by forgetting which way the >> goes when you want to specify the number of subtests in a test method.

So, I’ve done a quick fix of the infinite loop problem as well and uploaded version 0.3 to PAUSE, so now you can write your tests like:

testclass exercises ClassUnderTest {
    test creation of the class under test {
        lives_and {
            isa_ok ClassUnderTest->new, $test->subject;
        }
    }
    ...
}

without having to jump through the hoops of downloading a development version of Test::Class or worry about accidental infinite loops…

Next up, fix the syntax to either allow both << and >> as test count specifiers, or come up with a more memorable way of separating the count from the test name.

Update:

Shortly after I released 0.3, Joel Bernstein asked if I’d be interested in a topic branch to make Test::Class::Sugar work with perl 5.8.

“Of course!” I said.

One day later, there it was. Thank you to Joel and to his employers, NET-A-PORTER for sponsoring his work. So now, Test::Class::Sugar 0.4 is winging its way to CPAN and now I have no excuse for not using it at work.

Perl 5, version 10.1

Posted by Piers Cawley Mon, 24 Aug 2009 07:29:00 GMT

At last! Start your compilers everybody.

Perl: Not just a hobby any more 3

Posted by Piers Cawley Wed, 27 May 2009 05:11:00 GMT

Back in January, I wrote that I was choosing Perl to revisit as my language for recreational programming.

Quite a bit has happened since then. I gave a talk on MooseX::Declare to the London Perl Mongers and will be delivering an extended version at this year’s YAPC::Europe in August.

Then, a week ago, I accepted an offer to work full time as a Perl programmer again. In London.

Sing “Ho!” for the life of the long distance commuter.

London.pm Presentation Video

Posted by Piers Cawley Wed, 13 May 2009 13:28:00 GMT

Back in (crikey) February, I gave a talk at the London Perl Mongers’ technical meeting about Moose for Ruby Programmers and wrote it up here. Mike Whittaker was in the front row of the audience with his iPhone and, a couple of minutes in, started a voice recording and gave me a copy.

So… finally… I’ve taken the time I should have been using to write another article for The H and wrestled the slides and the audio into something like sync and uploaded the results to Vimeo for your viewing pleasure.

An introduction to MooseX::Declare from Piers Cawley on Vimeo.

Another conference season, another dumb sexist 6

Posted by Piers Cawley Tue, 28 Apr 2009 23:14:00 GMT

Mum was often the only women [at British Leyland sales conferences]. In those days it was apparently common for presenters to slip the occasional naked lady into the slides – “just to keep everyone awake”. When this happened, there’d be slightly embarrassed laughter and a few heads would turn to look at mum. Who ignored it. It doesn’t happen so often any more

That was me writing about Women in Open Source in 2005. This is Sarah Allen writing about Matt Aimonetti’s talk, “CouchDB + Ruby: Perform like a Pr0n star” which:

If he had left it [the dodgy images] at a few introductory jokes, I would be writing a very different post. Instead the porn references continued with images of scantily-clad women gratuitously splashed across technical diagrams and intro slides. As he got into code snippets, he inserted interstitial images every few slides (removed from the slides below). The first time it happened, he mentioned that he wanted to keep everyone’s attention.

Apparently, the difference between 80s truck salesmen and Matt’s audience is that at least 80s salesmen had the grace to look embarrassed.

Freedom is in Peril 1

Posted by Piers Cawley Fri, 10 Apr 2009 06:18:00 GMT

I’ve just written my first ‘real’ real post for the new Freedom is in Peril website. I’ll try and keep the political stuff on that blog from now on, but if you’re at all concerned about the erosion of what has traditionally, if cornily, been called “British Liberty”, then I hope you’ll swing by, and link to, the new site.

Test::Class::Sugar released 2

Posted by Piers Cawley Mon, 06 Apr 2009 10:15:00 GMT

I’ve just pushed the second version of Test::Class::Sugar (first discussed here). It’s pretty much as discussed in the original article, but after some discussion with David Wheeler, I’ve dropped the +uses clause from the testclass declaration in favour of less DWIMmy (and thus easier to explain behaviour).

I’ve also introduced a defaults argument at use time. The only working default in this release is one which lets you specify the prefix used to derive the test class name from the class/module under test. I’ve documented a couple of extra and so far unimplemented defaults as well.

Have a play, you might like it.

Keep calm and carry on my eye 1

Posted by Piers Cawley Sun, 22 Mar 2009 21:40:00 GMT

This seems like a more appropriate poster somehow:

Freedom Is In Peril Defend It With All Your Might

Can we have this in mugs, t-shirts and other formats please?

Thanks to Alan Fleming for pointing it out.



Just A Summary