Laziness, Impatience, and avoiding Excel

ziggy on 2005-01-14T03:09:29

It's that time of year again -- drafting up an annual budget.

At first, I fired up dc to get some back-of-the-envelope numbers. But then I wasn't sure if I had done the math properly. Time to find a better tool.

But I hate Excel. Every time I use it, I feel like I spend more time fighting with the tool than actually getting stuff done. The solution? Why, Perl of course!

#!/usr/bin/perl -w

use strict;

my $total = 0;   ## start with a clean balance

sub income ($$) {
    my ($type, $amount) = @_;

    $total += $amount;
    printf "%20s: +%6d %7d\n", $type, $amount, $total;
}

sub expense ($$) {
    my ($type, $amount) = @_;

    $total -= $amount;
    printf "%20s: -%6d %7d\n", $type, $amount, $total;
}
The rest of the script is self-documenting:
income "Yak Milking", 1000 * 12;  ## 1 yak, $1000/month

expense "Hosting",   100 * 12;
expense "DSL",   70 * 12;
expense "Electricity",  100 * 12;
expense "PowerBook", 3500;
expense "Mac mini", 599   ## G4, 1.42 GHz
                  + 425   ## 1GB RAM
                  + 149   ## AppleCare 
                  +  58   ## Kbd + mouse
                  + 999   ## 20" cinema display
;
expense "iPod",   500;
expense "iTunes Music Store", 30 * 12;
expense "ORA Books", 120 * 12;
expense "Jolt", 20 * 3 * 12;   ## three cases / month
...as is the output:
$ perl expenses.pl 
         Yak Milking: + 12000   12000
             Hosting: -  1200   10800
                 DSL: -   840    9960
         Electricity: -  1200    8760
           PowerBook: -  3500    5260
            Mac mini: -  2230    3030
                iPod: -   500    2530
  iTunes Music Store: -   360    2170
           ORA Books: -  1440     730
                Jolt: -   720      10
Hm...maybe I better rethink this career in yak milking.


You forgot:

delegatrix on 2005-01-14T20:02:37

print "You can't have one\n" for @fromApple;

Re:

Aristotle on 2005-01-14T23:30:31

Mmm, neat! I wanted to get one little bit of syntactic sugar in there, but that made it more complicated than I was hoping:
use strict;
use warnings;

package Calc;

use base 'Exporter';
our @EXPORT = qw( income expense monthly );

sub income {
    my ( $type, $amount ) = @_;
    __PACKAGE__->new( $type, $amount );
}

sub expense {
    my ( $type, $amount ) = @_;
    __PACKAGE__->new( $type, -$amount );
}

sub monthly {
    my $self = shift;
    $self->{ amt } *= 12;
    return $self;
}

sub new {
    my $class = shift;
    my ( $type, $amount ) = @_;

    bless {
        type => $type,
        amt  => $amount,
    }, $class;
}

my $total = 0;  # start with a clean balance

sub DESTROY {
    my $self = shift;
    $total += $self->{ amt };
    my $sign = $self->{ amt } < 0 ? "+" : "-";
    printf "%20s: %s%6d %7d\n", $self->{ type }, $sign, abs( $self->{ amt } ), $total;
}

1;
So after all that mumbojumbo you can now say
use Calc;

monthly income 'Yak Milking' => 1000;
monthly expense Hosting => 100;
expense PowerBook => 3500;
# ...
and you'll get the same output as you had.

Re:

ziggy on 2005-01-15T16:23:47

That's a really good idea. Here's a different approach:
no strict 'refs';
sub monthly ($$$) {
    my ($function, $type, $amount) = @_;
    &$function($type, $amount * 12);
}

sub quarterly ($$$) {
    my ($function, $type, $amount) = @_;
    &$function($type, $amount * 4);
}

sub biweekly ($$$) {
    my ($function, $type, $amount) = @_;
    &$function($type, $amount * 26);
}

sub weekly ($$$) {
    my ($function, $type, $amount) = @_;
    &$function($type, $amount * 52);
}

monthly   income  => "Yak Milking", 1000;
quarterly expense => "Hosting", 250;
biweekly  expense => "Health Plan", 150;
weekly    expense => "Dog Walking" => 100;
(OK, I admit it. I've been inhaling from the scheme pipe lately. ;-)

Re:

Aristotle on 2005-01-15T22:15:09

Yuck. I don't think the Schemers have much more appreciation for symrefs than the Perlistas. :-)

The difference is I wanted to be able to easily throw more qualifiers into the mix:

sub bi {
    my $self = shift;
    $self->{ amt } /= 2;
    return $self;
}

# and later:

bi monthly expense Whatever => 100;

It didn't start out OO. Initially I tried to just pass closures up the call chain. That didn't work too well though: I had to check wantarray everywhere to do the printing. Can you say duplication? So I blessed the closure into a package with a sub DESTROY { $$self->() }. Then I moved the printing into the DESTROY itself. And well, then I noticed it was much easier to stick the thing into a class, throw out all the repetition, and call it a day. :-)