clojure

2025-12-15T10:12:44.736559Z

Does anyone have experience working with monetary amounts? I've looked at bankster and it seems to do the trick in terms of maintaining arithmetic rules (like "don't sum different currencies") but I am unsure whether having a glorified numeric type is idiomatic clojure

igrishaev 2025-12-15T11:41:00.983199Z

This question is mostly related to a database, not Clojure I think. Each client has accounts, and each account has currency. Then, you have a table that stores history of account withdrawals and replenishments. It's a matter of SQL stored function that accepts an account id, a sum, and a currency and checks if the currency matches the currency of the account. Otherwise, the function throws an exception

cjmurphy 2025-12-15T17:35:55.253469Z

I like to use int (which is a Java long) rather than float or a BigDecimal type. Keep all monetary stored data as a type without decimal places in the database - so cents or pesos or whatever. For presentation or reading input convert to the integer type as soon as the number is within your program. After multiplication or division you are going to have to round.

2025-12-15T10:15:42.087119Z

Also, currencies in my mind are a lot like physical units, just with Brownian motion as conversion function rather than constants, so just out of curiosity, has anyone established a pattern to work with units as well?

cjohansen 2025-12-15T10:22:19.663419Z

@teodorlu recently designed a sweet open ended system for working with units: https://github.com/teodorlu/munit

🥳 2
teodorlu 2025-12-15T10:52:18.336089Z

Happy to help if you'd like to give Munit a go! The core idea is small: 1. Represent numbers with units as Clojure data 2. Provide math operators for working on that data structure. There are other Clojure unit libraries too, but they generally work on a custom type for numbers with units, and a larger API surface than data + arithmetic operations.

teodorlu 2025-12-15T10:57:51.062549Z

Munit doesn't care if your base units are Euros, meters or apples, so there's no arbitrary unit system restrictions in place.

(+ [1 'apple] [1 'apple])
;; => [2 apple]

(+ [1 'apple] [1 'orange])
;; => Execution error (ExceptionInfo) at munit.impl/add (impl.clj:106).
;;    Cannot add quantities of different units

2025-12-15T10:58:06.573859Z

So I take it this is more of a DSL-ish approach?

teodorlu 2025-12-15T10:58:35.134589Z

No DSL, it's lighter! Units are plain Clojure data, and + is a normal function, munit.units/+.

2025-12-15T10:59:44.321379Z

Sweet. Even better.

❤️ 1
2025-12-15T11:01:03.188889Z

Aaand, I was just going to ask whether you have any property-based tests, but then I found it as a to do in the code^^

2025-12-15T11:02:26.463139Z

You surely wouldn't mind a PR in case I find some time to build a few of those?^^

teodorlu 2025-12-15T11:04:21.586389Z

I'd happily take a test.check generator for Munit! Not sure about tests, I wasn't able to convince myself of any interesting properties to test. If your generator & properties does find any bugs, I'll definitely merge!

2025-12-15T11:37:28.358519Z

I'll look into it! My question might well have been a case of hammer-nail-fallacy, so what's there might just be enough, but we'll see^^

teodorlu 2025-12-15T11:50:16.674859Z

Let me know how it goes! Regardless of PRs, I'm very much interested in helping you get started, and want to know if you hit any friction!

🥰 1
2025-12-15T11:56:17.814559Z

though I later abandoned it in favor of just using millis for everything (this was time only), so ymmv

2025-12-15T12:02:46.422679Z

First thing that I noticed: is there an intended way of defining new units (not derived)? So I wanted to use this for money, I'd probably do something like (def $ (units/* 1 1)). Is that how you'd imagine it?

teodorlu 2025-12-15T12:12:09.987069Z

If you're asking me and not potetm — base units in Munit are symbols. You can either inline the symbol:

(* 2 [7 '$])
;; => [14 $]
Or make yourself a namespace for your new base units, and use that.
;; currencies.clj
(ns currencies)

(def $ '$)
(def EUR 'EUR)
(require 'currencies)
(* [7 currencies/$]
   [0.9 (/ currencies/EUR currencies/$)])
;; => [6.3 EUR]

teodorlu 2025-12-15T12:14:50.557069Z

I generally prefer putting base units in a namespace, that way currencies/EURR fails fast (typo), and I can use static analysis to find where "EUR" is used.

teodorlu 2025-12-15T12:16:33.615619Z

If you define dollars as (def $ (units/* 1 1)), $ will bind to the number 1! So not a Munit base unit.

2025-12-15T20:14:55.510849Z

i'm trying to write an fdef spec for a macro i have, which looks like extend-protocol. The macro accepts calls like this (defhook profile (pre-test-run [config m] ...) (post-test-run [config m ...])), and i'm looking to verify that each "protocol"-like definition is a simple-symbol followed by a params+body. i have a spec that i would expect to work but doesn't. i'll post in the thread the spec i've come up with

2025-12-15T20:15:10.714959Z

(spec/def ::body
  (spec/cat :params (spec/spec :clojure.core.specs.alpha/param-list)
            :body (spec/* any?)))
(spec/def ::impl
  (spec/cat :hook-name simple-symbol?
            :hook-tail (spec/+ ::body)))
(spec/def ::defhook-args
  (spec/cat :defhook-name simple-symbol?
            :hook-impls (spec/+ ::impl)))

2025-12-15T20:15:41.781539Z

running

(spec/explain
 ::defhook-args
 '(profile (pre-test-suite [_config m] (start m))))
produces
(pre-test-suite [_config m] (start m)) - failed: simple-symbol? in: [1] at: [:hook-impls :hook-name] spec: :lazytest.hooks/impl

2025-12-15T20:17:35.217539Z

why is ::impl being treated as inline to the :hook-impls part of s/cat?

2025-12-15T20:18:20.945199Z

after poking around, i figured out i can say :hook-impls (spec/+ (spec/and seq ::impl)) to make it work, but i don't understand why

2025-12-15T20:30:55.059989Z

oh, i need :hook-impls (spec/+ (spec/spec ::impl)) because i'm starting a new nested context

Alex Miller (Clojure team) 2025-12-15T20:57:13.480949Z

there is a spec for protocols in a jira if you wanted to swipe that btw

👀 1
Alex Miller (Clojure team) 2025-12-15T20:57:38.557329Z

regex specs all combine into one "nesting level", s/spec can be used to create a new one

👍 1
Alex Miller (Clojure team) 2025-12-15T20:59:28.142949Z

well, maybe there isn't a jira for that, can't find it now. Sure thought I had worked on it at some point

2025-12-15T21:00:00.665059Z

there's one for extend-protocol but i don't think there's an attached patch: https://clojure.atlassian.net/browse/CLJ-2836

2025-12-15T21:00:12.716279Z

there's no doubt other tickets i don't remember/know about