Perl, Haskell, stuff
A couple of comments on the first post suggested that I look into the command-line bookkeeping application ledger, or indeed, its Haskell version hledger. They look very interesting, but rather hard to wrap my head around. So though I'm going to bear them in mind for later, I'll carry on doing these sketches till I understand the problem space better, at which point, perhaps I'll either a) steal some ideas from them, or b) realise they are undoubtedly the way forward and start using them.
I used Module::Starter to retrospectively turn this project into something I can release as a distribution to CPAN, with docs, tests, a Makefile, and so on.
module-starter --module=Beans --mi --author=osfameron --email=osfameron@cpan.org
This also creates some skeleton docs, so I've gone in to add a few actual notes (mainly just pointing at this blog), and to delete a few sections that module-starter puts in by default:
Then I added some tests in t/01-basic.t, for example:
my $item = Beans::Item->new(
name => 'Mortgage',
value => 500.00,
due_date => '2009-10-01',
comment => 'Home sweet home',
tags => [qw/ mortgage foo bar /],
);
ok $item, 'Object created successfully';
is $item->name, 'Mortgage', 'Name ok';
is $item->value, 500, 'Value ok';
is $item->due_date->month, 10, 'Date ok';
All very noddy stuff, but it helped me find a bug in the version I'd blogged earlier! I hadn't told the date fields to use the coercion I'd set, so the above code failed, complaining quite rightly that '2009-10-01' isn't a DateTime object!
So I amended the date fields like so:
has due_date => ( isa => DateTime,
is => 'rw',
required => 1,
coerce => 1,
);
and all was again well. Yay for failing tests! This brings me to a suggestion from John Napiorkowski to use MooseX::Types::DateTimeX to get my coercion for free. This does indeed work, and I've changed the code to use it, though it doesn't by default use DateTime::Format::Natural — we'll come back to this later.
While we're looking at types, let's fix the crufty implementation of tags. We've currently got an ArrayRef[NonEmptyStr], but really, we don't want an Array, because we want to be able to:
This changes our code to:
has tags => (
isa => 'Set::Object',
is => 'rw',
accessor => '_tags',
coerce => 1, # also accept array refs
handles => {
tags => 'members', # random order
add_tag => 'insert',
remove_tag => 'remove',
has_tag => 'member'
},
);
which we can test like so:
is_deeply [ sort $item->tags],
[qw/ bar foo mortgage /], 'Tags ok'
or diag Dumper($item->tags);
ok $item->has_tag('foo'), 'Has tag foo';
ok $item->has_tag('bar'), 'Has tag bar';
ok $item->has_tag('mortgage'), 'Has tag mortgage';
ok ! $item->has_tag('baz'), 'Nonexistant tag baz';
$item->add_tag('baz');
ok $item->has_tag('baz'), 'Now has baz';
$item->remove_tag('foo');
ok ! $item->has_tag('foo'), 'Now lost tag foo';
Set::Object's members function returns the contents in hashed order (i.e., effectively random), but given that we know our "objects" are actually strings, I'd prefer them in sorted order, which would simplify the is_deeply test above. We could do this as a method instead, or possibly use around to sort the results, but we'll come back to this soon!
Osfameron's blog on Haskell, Perl programming, stuff.
Leave a reply