The moose counts beans – managing my finances with Perl

I’ve recently been trying to improve my rather disorganized personal finances.
While I previously “managed” by leaving the bulk of the money in my account
and hoping that I’d calculated my expenses versus salary roughly correctly,
this is suboptimal for answering various questions like “Do I have enough
money to go on holiday?” or “Have I forgotten to pay the electricity bill
for the last year so that I now have to pay it back?”

Clearly there is a better way. Checking off expected payments, knowing what’s
due in the next week/month/etc., and siphoning off the excess into a linked
savings account for example. But this requires being alert and accurate…
Failing that, I could always use a computer ;-)

Spreadsheets

Surprisingly perhaps, the humble spreadsheet is an instrument of frustration
for this kind of task. I’m currently using Google Docs, which is underpowered,
but not really much more hateful than Excel or OpenOffice.org.

It’s particularly bad at being a working document. Moving an item (to
reschedule the date, for example) involves:

  • inserting rows
  • cutting and pasting
  • deleting the original rows
  • changing the date
  • “filling” down the subtotal column (because spreadsheets don’t have the concept of a calculated column).

Worse yet, because spreadsheets don’t know about dates, adding regular
payments (bills, weekly spends etc.) is an exercise in manually creating them,
and then visually auditing the sheet to make sure you put them in the right
places.

Other apps

Of course dedicated apps for personal finances do exist. I looked at the
Linux ones, Gnucash, Kash, etc. Mostly they crashed. Sometimes they ran
but utterly confused me. And then crashed.

There are also web based ones. My initial survey of these suggests that
they are prettier and easier to use, but perhaps clunkier, or tied into
specific bank systems.

Beans means…?

So, being a programmer, the obvious answer is to attempt to roll my own.
As a learning exercise ;-) Though I’ll attempt various sketches of this in
Haskell, I’ll start off by trying to implement it in Modern Perl:
specifically the
Moose
OO framework, using various niceties such as
the pretty declarative syntax of
MooseX::Declare.

Every project needs a good name! But this is just a learning exercise, so for
now I’ll call it “Beans”.

I’m hoping this will end up an ongoing series of posts, please feel free to
follow along at the
Beans git repo if you like,
or to suggest improvements, for example:

  • a better project name
  • shinier ways to do this in Moose and Modern Perl
  • how this would be more elegant in Haskell/Lisp/etc…

As always, these posts will be fairly rough drafts, I’m hoping to tidy this up
into a more finished article soon, so any comments are really welcome to help
me polish it up!

Declarative code

In this installment, I’ll just define a data type for a line item in Beans.
You can see the current code as of this evening,
but let’s look at it in detail here.
We’ll start off by using Moose, and declaring our class:

use MooseX::Declare;

class Beans::Item {
    ...
}

Now we’ll want to declare some fields for our class. For example, every
payment made or expected will have a value:

    has value     => (
                       isa      => Num,
                       is       => 'rw',
                       required => 1,
                     );

What’s that? Perl has types?! Yes indeed, Moose gives us types, and
automatically validates against them. So when we try to create our object,
we’d find that:

    my $obj = Beans::Item->new( );                # fails, value is required!
    my $obj = Beans::Item->new( value => "foo" ); # fails, "foo" is a string
    my $obj = Beans::Item->new( value => 10.00 ); # OK! 10.00 is a number

I’ve cheekily missed out a few details though… Num isn’t a Perl
builtin keyword, so we’ll need to import it. In fact, while we’re at it, let’s
pull in Str too, so that we can add a couple of string fields at the
same time:

    use MooseX::Types::Moose     qw/ Num Str /;

    has name      => ( isa      => Str,
                       is       => 'rw',
                       required => 1,
                     );

    has comment   => ( isa => Str,
                       is  => 'rw',
                     );

Now we also want to be able to add the date of the expected payment:

    use MooseX::Types::DateTime  qw/ DateTime /;

    has due_date  => ( isa      => DateTime,
                       is       => 'rw',
                       required => 1,
                     );

This means we could do

    my $obj = Beans::Item->new(
        value    => 10.00,
        due_date => DateTime->new(
            year  => 2010,
            month => 6,
            day   => 30,
           ),
      );

        # but I'd prefer to be able to do
        due_date => '2010-06-30',

And we can indeed make this prettier using coercions!

    use DateTime::Format::Natural;
    my $dt = DateTime::Format::Natural->new( prefer_future => 1 );
    coerce 'DateTime'
        => from 'Str'
            => via { $dt->parse_datetime($_[0]) };

    # We'll need to tweak the due_date declaration too...
    # (coercions don't run unless you ask for them)

    has due_date  => ( isa      => DateTime,
                       is       => 'rw',
                       required => 1,
                       coerce   => 1,
                     );

While every line item will have an expected due_date, we’ll also want
to keep track of what date the item was actually paid, if it has
been paid. As this is uncertain, we use the Haskell-inspired Maybe
type. (As far as I can see, this isn’t actually as powerful as Haskell’s
Maybe monad, it’s more like a nullable type).

    use MooseX::Types::Moose     qw/ Num Str Maybe /;

    has paid_date => ( isa => Maybe[DateTime],
                       is  => 'rw',
                     );

Once we’ve got this type, we can calculate a boolean field based on it.
The paid field will be true if paid_date contains anything,
and false otherwise. Let’s try defining it like this:

    use MooseX::Types::Moose     qw/ Num Str Maybe Bool/;
    has paid      => ( isa => Bool,
                       is  => 'rw',
                       lazy => 1,
                       default => sub {
                            my $self = shift;
                            defined $self->paid_date
                          },
                     );

The default code block gets called the first time we ask for
the field. There are a couple of problems with this of course – once it’s
set, if we change the paid_date, this field won’t change. We’ll come back to
this later, but here are some ideas:

  • change it into a method
  • use immutable objects, so that setting this lazily on first request is
    always the right thing to do

  • use triggers to update paid when paid_date is set
    (and vice versa! setting the boolean flag could set the payment date to
    today’s date, for example).

Now, though we specified that the name should be a string, nothing is
preventing us from passing the empty string ""… but Moose allows
us to define a better type constraint here too!

    use MooseX::Types -declare => [qw/ NonEmptyStr / ];
    subtype NonEmptyStr
        => as Str
            => where { length $_ };

    # so now we can modify this definition to:

    has name      => ( isa      => NonEmptyStr,
                       is       => 'rw',
                       required => 1,
                     );

>

Finally, let’s add a list of tags. Just for now, we’ll define these as an
array of non-empty strings:

    use MooseX::Types::Moose     qw/ Num Str Bool Maybe ArrayRef /;

    has tags      => ( isa      => ArrayRef[NonEmptyStr],
                       is       => 'rw',
                       default  => sub { [] },
                     );

Notice how we’re using default again to set the default value to
an empty array.


That’s it for now! Next time around we’ll maybe look at parsing and displaying
line items, and start to do useful things with them.