tmbb

tmbb

Plaintext accounting as a scripting language

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.

Most Liked

sbuttgereit

sbuttgereit

Sorry, but an accounting nit which admittedly is besides the point of your post. The transaction below…

… looks wrong to me. You’re showing that you’re paying a liability, the amount that you owe someone, without ever having booked an expense for which that liability was incurred. Typically, for an incurred tax liability you’d have two transactions.

When the income is earned and on which the taxes are levied:

txn ~D[2025-07-09], notes: "account", accounts: %{
  account("Income::Studio rent") => ~A[-800 EUR],
  account("Expense::Tax::Taxes on rent") => ~A[224 EUR],
  account("Liabilities::Tax::Taxes owed") => ~A[-224 EUR],
  account("Assets::Bank::Checking") => ~A[800 EUR]
}

Later when the taxes are paid:

txn ~D[2025-08-01], notes: "account", accounts: %{
  account("Liabilities::Tax::Taxes owed") => ~A[224 EUR],
  account("Assets::Bank::Checking") => ~A[-224 EUR]
}

This represents that between earning the income which causes the taxes and the actual payment of the taxes, the money was sitting in your checking account. Naturally, if the taxes are paid straight-away, you’d just book the expense and never go to the liability account at all. There’s more to say about this… but this is a forum for Elixir and not accounting… so I’ve already said too much :slight_smile:. Also, I’m not an accountant, but am forced to play one in my daily work with uncomfortable regularity… so I could be completely wrong!

waseigo

waseigo

Double-entry book-keeping is something that I’m interested in. I played around with Tigerbeetle many moons ago and found it impressive, but also overkill for my needs.

At some point I’ll build something using this: GitHub - coinjar/ex_double_entry: An Elixir double-entry library inspired by Ruby's DoubleEntry. Brought to you by CoinJar.

It might be useful for you too.

Where Next?

Popular in Discussions Top

heathen
Quite interesting article Google brought me. Didn’t find any mentions about it here. What do you think in general? Would you use togethe...
New
dimitarvp
Hello, I am looking to more closely understand the advantages of the Repo.transact pattern and the quoted disadvantages of Ecto.Multi. ...
New
lud
Hello, I just extracted the boilerplate management code that I used to work with in previous years: It is yet another generic input d...
New
szsoppa
Hey folks! For the third year in a row, we’re running the Elixir Survey by Curiosum! It’s been live since yesterday, and 300+ people hav...
New
James_E
I see that the current ExUnit source code has support for rich failure messages on a small whitelist of “recognized” assertion patterns, ...
New
garrison
I have been thinking about how I might write a new web framework in Elixir. (Not committing to anything, just thinking about it.) One th...
New
marciol
Thinking about how Contexts on Phoenix 1.3 are beautiful in the simplicity, elegance and minimalism, coalesced in the smallest unity of E...
New
hauleth
With OTP 21 and the creation of logger Elixir Logger application became quite limiting (no structured logging). I would like to discuss w...
New
adam
I wonder if anyone in the community has started to use Github’s Codespaces for their team’s development? If so, what is there to learn? ...
New
ronindev
Hey everyone! :waving_hand: I just wanted to recommend https://seenode.com/ for deploying Phoenix apps. I’ve been using it recently and ...
New

Other popular topics Top

peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement