During our trip over the extended weekend, we quickly decided it would be easier if different persons buy various stuff everyone needed (say, group tickets, lunch and booze). So, everyone started to pay lots of different things with his/her own money, and it seemed like it would get a big mess.
Ruby to the rescue! I quickly (and I mean it, I coded while the others made breakfast) came up with a script to calculate who needed to give what amounts of money to other persons so everyone spent the same amount of money—all in all.
In the beginning, it was a very easy task. We created a simple file formatted like this:
# Name Amount Comment
Mr. Foo 72.00 Tickets
Lil' Bar 33.00 Booze
The people that didn’t pay anything just were listed, with empty amount and comment:
Squarehead
J. Random Hacker
Now, we would run the program and get an output like that:
J. Random Hacker -> Lil' Bar: 8.25
J. Random Hacker -> Mr. Foo: 18.00
Lil' Bar -> Mr. Foo: 9.75
Squarehead -> Lil' Bar: 8.25
Squarehead -> Mr. Foo: 18.00
4 Leute, 26.25 pro Person (Cashflow: 105.00)
Lil' Bar: Booze
Mr. Foo: Tickets
On a glance, you see who needs to pay whom what amount and what for. Some throwaway results are presented too.
Of course, that script was far too easy (the initial version didn’t sort the lines, so it was even easier, but I didn’t use VC, so it’s lost in the mists of time). Have a look at the source.
Soon, trouble started. One person came on the next day on her own, so she wouldn’t need to pay the group tickets (that’s only fair), but of course the other stuff we bought. I simply rewrote the program to divide the money among the known people at that time, and list everyone at the beginning and the one that came late just later. It worked fine and was a quick adjustment.
On the second day, we decided to make the first cashing-up.
Obviously, we wouldn’t want to remove the entries so far (they were
needed for the statistic, too), but they shouldn’t be calculated
anymore. I hacked some code to make ---
output the list and delete
the current debts.
Then, there was someone that would leave sooner, and of course didn’t
want to pay things she couldn’t make use of. Another hack, and -Name
lines remove the person from the known people. This messes up the
statistic a bit, but we didn’t really care.
The source code of our final version is available too.
Now, when I revisit the code, it’s very much like a awk
script to
me. You can see the typical elements, no additional classes were
defined, only hashes and arrays store the data. The main program is a
loop to parse, and run stuff at the same time. It’s rather icky with
it’s linear, but messed flow.
But I can do better. Let’s build a domain specific language to manage group money! Now, the input file (a valid Ruby program, actually) looks like this:
person :MrFoo
person :LilBar
person :Squarehead
person :JRHacker
MrFoo.pay 72.00, "Tickets"
LilBar.pay 33.00, "Booze"
cashing_up!
The last version (for now, at least) is only two lines longer than the previous one, but lets Ruby do the parsing. This is actually very useful. For example, we could now also write:
EGG = 0.33
JRHacker.pay 12*EGG, "a dozen eggs"
There are further ways to clean up the code. For now, shared state is saved
in constants and globals, we could create a wrapper class to take care
of that (even instance_eval
the file inside it, and use $SAFE
),
but for now, and especially for such a simple program, this is not
needed.
I have demonstrated the usefulness of a general-purpose scripting language to ease tasks that do happen on the road and wouldn’t be a lot of fun to calculate manually (YMMV, but if you have lots of entries, you need to calculate a lot). Additionally, I showed how a flexible object-oriented language like Ruby can be used to create mini-languages to save parsing overhead and make use of the language features.
I wish you well on your travels
My friends I wish you well along the way
— Dan Bern, Jail
NP: Manu Chao—La Despedida