Wednesday, August 27, 2014

Moo and Types

Wow. I haven't posted anything on here about programming in what seems like forever. Well, to be honest, I haven't been doing that much programming outside of work that's been worth blogging about, but now here we are!

For a number of years now, the post-modern object systems have been making a lot of head-way in the Perl community. We use Mouse at work, and in my own little projects, however small they may be, I've been using Moo. Why? Quicker startup than Mouse (and of course Moose), has the features that I really care about and use most in Mouse and doesn't install half of CPAN along with it.

For a first, testing script, I generally write classes to model people. And of course, people have a date of birth, which can be neatly represented with a DateTime object. But no Moo-compatible DateTime type existed out of the box (that may have changed now, but not at the time). Brilliant! So now I got to write my own Moo type, which would be nice as an introduction to the library, as writing your own types is something you're inevitably going to do.

First I found MooX::Types::MooseLike, which pretty much did what I wanted. So I wrote MooX::Types::MooseLike::DateTime (and released it only recently despite the fact that I'm blogging about a better method). The backend code was a little verbose and coercions had to be done in the type declaration. But whatever, it worked. So my test script continued on.

And then I found Type::Tiny. And it's compatible with Moose and Mouse aswell as Moo! The backend code is quite compact and coercions can be easily implemented.

Here's my type declaration.

package MyTypes;

use Type::Library -base, -declare => qw/DateTime/;
use Types::Standard qw/Str Int Object/;
use Type::Utils qw/class_type coerce from via/;

use DateTime::Format::Strptime;

class_type DateTime => { class => 'DateTime' };

coerce 'DateTime',
 from Int, via { DateTime->class->from_epoch(epoch => $_) },
 from Str, via { DateTime::Format::Strptime->new(pattern => '%F %T')->parse_datetime($_) };

1;

Something I really like here is that I can declare a DateTime type and access the underlying DateTime class with 'DateTime->class', so there's no need to import the original DateTime class, or alias or quote it so it doesn't clash with my type. And the coercions read very easily.

Here's the Person class that utilises the type.

package Person;

use Moo;
use MyTypes qw/DateTime/;

has dob => (
 is      => 'rw',
 isa     => DateTime,
 coerce  => DateTime->coercion,
 default => sub { DateTime->class->today }
);

1;

If I wanted to use Moose or Mouse instead, I could change the use statement at the top, and then change the coerce parameter to a value of '1'. EDIT: With Moo 1.006000 you can do the same with Moo now ;)

And here's the driver script.

#!/usr/bin/env perl

use warnings;
use strict;

use Person;
use DateTime;

print Person->new(dob => time)->dob->iso8601, "\n";
print Person->new(dob => '2013-01-01 13:37:00')->dob->iso8601, "\n";
print Person->new(dob => DateTime->today)->dob->iso8601, "\n";
print Person->new->dob->iso8601, "\n";

Simple and versatile.