Plaintext accounting as DSL for finance
Plaintext accounting is a whole philosophy which tries to get the best of both simple paper ledgers (for maximum flexibility and portability) and the capabilities of modern computers (for maximum error-avoidance). Some interesting software in this category is ledger, hledger (similar to ledger, but implemented in haskell) and beancount (similar to the ones above, implemented in python).
I think all the above programs have some important flaws (ledger is actually not very good, hledger seems to be a bit inplexible with imports and beancount makes it very hard to import transaction data whithout looking at the source and writing custom python scripts). I actually like the flexibility of beancount, but I’ve decided to go a little further and implement a ledger as a DSL on top of Elixir.
The idea is very simple: a ledger is a sequence of elixir function calls (which use the process dictionary for shared mutable state, which is safe because everything runs in order) which build a ledger datastructure in the background. This allows us to have an unambiguous grammar with some goodies such as Elixir sigils.
For example, this very simple ledger which features the opening of accounts, a 3-way transaction and a balance check (the numbers are random, nothing actually checks). The functions in this file only generate the ledger, they don’t actually check anything, that is left for an as of yet unimplemented module.
open ~D[1990-04-25], account("Expenses::Groceries"), currency: "EUR", note: "track all groceries"
open ~D[1990-04-24], account("Expenses::Eating out"), currency: "EUR"
open ~D[1990-04-24], account("Expenses::Going out (other)"), currency: "EUR"
open ~D[2010-04-24], account("Assets::Bank::Checking"), currency: "EUR"
open ~D[1990-04-24], account("Assets::Cash"), currency: "EUR"
open ~D[2010-04-24], account("Income::Salary"), currency: "EUR"
open ~D[2010-04-24], account("Income::Studio rent"), currency: "EUR"
txn ~D[2025-07-09], notes: "account", accounts: %{
account("Income::Studio rent") => ~A[-800 EUR],
account("Liabilities::Tax::Tax owed on rent") => ~A[224 EUR],
account("Assets::Bank::Checking") => ~A[576 EUR]
}
balance ~D[2025-07-09], account("Assets::Bank::CGD::Checking"), ~A[200 EUR]
Has anyone here ever tried something like this?
I think this doens’t attempt to sandbox the elixir execution, so a maliciously constructed ledger could do bad things to your computer. However, I believe, it might be easy to sanbox it if one disallows the use of qualified functions.