A simple way to manage your household budget with Common Lisp and TravisCI

enzu.ru
4 min readAug 14, 2020

I really don’t like spreadsheets, even the ones provided by the excellent org-mode which I use for other purposes.

For one, they are too obsessed with location and order. I would like to re-order items within my household budget at any moment due to pure whimsy and not have things break. There is probably a way to do this with spreadsheets, but there is certainly an easier way with code.

Which brings me to another point: as a programmer, I’m more comfortable with code than spreadsheets. I not only get to see how my household budget has changed over the years through git commits, I can easily check previous builds with TravisCI to see the actual numbers generated by my budget.

And like all Lisps, Common Lisp represents “code as data”, an attribute referred to as homoiconicity. This leaves open the door of possibility for my budget to eventually write its own code. Additionally, using SLY or SLIME allows me to hack my budget live in a REPL, as opposed to being stuck with the “write — compile — debug” loop most programming environments force upon you.

And lastly, you can do this for completely free with a private GitHub repo and TravisCI.

To get started, first write some brief code in a file called finances.lisp:

(defparameter *monthly-income* 1000)
(defparameter *index-by-category* (make-hash-table))
(defparameter *index-by-card* (make-hash-table))
(defun add-liability (label amount category card)
(if (gethash category *index-by-category*)
(setf (gethash category *index-by-category*) (+ amount (gethash category *index-by-category*)))
(setf (gethash category *index-by-category*) amount))
(if (gethash card *index-by-card*)
(setf (gethash card *index-by-card*) (+ amount (gethash card *index-by-card*)))
(setf (gethash card *index-by-card*) amount)))
(defun print-budget ()
(format t "~%Budget by category~%~%")
(let ((total 0))
(loop for k being each hash-key of *index-by-category*
do (setf total (+ total (gethash k *index-by-category*))))
(loop for k being each hash-key of *index-by-category*
do (format t "~A: ~$ (~$%)~%"
k
(gethash k *index-by-category*)
(* 100 (/ (gethash k *index-by-category*) *monthly-income*))))
(format t "LEFTOVER: ~$ (~$%)~%"
(- *monthly-income* total)
(* 100 (/ (- *monthly-income* total) total))))
(format t "~%Card totals~%~%")
(loop for k being each hash-key of *index-by-card*
do (format t "~A: ~$ ~%" k (gethash k *index-by-card*))))

The first function (add-liability) just indexes my budget totals into hash tables by category (*index-by-category*) and payment card (*index-by-card*). The second function (print-budget) organizes and prints the data from the hash tables in a user-friendly manner.

Now, adding items to my budget is as simple as passing the name as a string, the cost as a number, the category and payment card as symbols; these symbols don’t need to be defined in advance, just make up a symbol whenever you want to use it. Again, this is added to finances.lisp:

(add-liability "Cellphone" 50 :bills :amex)
(add-liability "Internet" 50 :bills :discover)
(add-liability "Rent" 400 :home :debit)
(print-budget)

Then I have a very short .travis-ci.yml file that runs the above script on every git commit using the fast and portable CLISP interpreter:

language: generic

before_install:
- sudo apt-get install clisp

script:
- clisp finances.lisp

Once I have enabled TravisCI to access my GitHub repo, after each git commit is pushed to GitHub, a TravisCI build triggers with an output like:

Budget by categoryHOME: 400 (40%)
BILLS: 100 (10%)
LEFTOVER: 500 (50%)
Card totalsAMEX: 50 (5%)
DISCOVER: 50 (5%)
DEBIT: 400 (40%)

Lastly, I add a TravisCI build badge to my project so that I can ensure it is passing and so that I can easily click on it from the README.md on GitHub.

One idiosyncratic aspect of my workflow is that I oftensly-eval-buffer with C-x c whatever script I am working on instead of gently evaluating or REPL’ing a few lines at a time. I just find this quicker for resetting state and finalizing the source file; I reserve the REPL for explorative programming and general testing. Meaning, I’d rather sly-eval-buffer my finances.lisp which will reset my two hash maps (*index-by-category* and *index-by-card*) and auto-refill their entries instead of slowly adding and removing liabilities one at a time in a REPL.

A more Lisp-y solution might be to allow for the code to add or remove a liability by name, as opposed to just indexing the cost by category and payment card. That will be left as an exercise to the interested reader.

--

--

enzu.ru

Learning content for the GNU operating system and Lisp user space, developed through my eponymous charity (https://enzu.ru)