Perl, Haskell, stuff
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 ;-)
It's particularly bad at being a working document. Moving an item (to reschedule the date, for example) involves:
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.
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:
Now we'll want to declare some fields for our class. For example, every
payment made or expected will have a value:
use MooseX::Declare;
class Beans::Item {
...
}
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:
has value => (
isa => Num,
is => 'rw',
required => 1,
);
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:
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
Now we also want to be able to add the date of the expected payment:
use MooseX::Types::Moose qw/ Num Str /;
has name => ( isa => Str,
is => 'rw',
required => 1,
);
has comment => ( isa => Str,
is => 'rw',
);
This means we could do
use MooseX::Types::DateTime qw/ DateTime /;
has due_date => ( isa => DateTime,
is => 'rw',
required => 1,
);
And we can indeed make this prettier using coercions!
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',
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 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,
);
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 /;
has paid_date => ( isa => Maybe[DateTime],
is => 'rw',
);
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:
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
},
);
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.
Osfameron's blog on Haskell, Perl programming, stuff.
Tim
September 17th, 2009 at 2:32 am
Warning: preg_replace_callback() [function.preg-replace-callback]: Unknown modifier '|' in /var/www/blog/wp-content/plugins/text-control/text-control/markdown.php on line 766
Rocco Caputo
September 17th, 2009 at 2:56 am
Warning: preg_replace_callback() [function.preg-replace-callback]: Unknown modifier '|' in /var/www/blog/wp-content/plugins/text-control/text-control/markdown.php on line 766
Moritz Onken
September 17th, 2009 at 3:38 am
Warning: preg_replace_callback() [function.preg-replace-callback]: Unknown modifier '|' in /var/www/blog/wp-content/plugins/text-control/text-control/markdown.php on line 766
John Napiorkowski
September 17th, 2009 at 6:40 am
Warning: preg_replace_callback() [function.preg-replace-callback]: Unknown modifier '|' in /var/www/blog/wp-content/plugins/text-control/text-control/markdown.php on line 766
P .Assini
September 17th, 2009 at 8:23 pm
Warning: preg_replace_callback() [function.preg-replace-callback]: Unknown modifier '|' in /var/www/blog/wp-content/plugins/text-control/text-control/markdown.php on line 766
Beans pt2: docs, tests, and more types - Just another lambdabananacamel,
October 9th, 2009 at 2:06 pm
Warning: preg_replace_callback() [function.preg-replace-callback]: Unknown modifier '|' in /var/www/blog/wp-content/plugins/text-control/text-control/markdown.php on line 766