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
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
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.
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?
@teodorlu recently designed a sweet open ended system for working with units: https://github.com/teodorlu/munit
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.
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 unitsSo I take it this is more of a DSL-ish approach?
No DSL, it's lighter! Units are plain Clojure data, and + is a normal function, munit.units/+.
Sweet. Even better.
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^^
You surely wouldn't mind a PR in case I find some time to build a few of those?^^
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!
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^^
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!
here's an example of a doityourself approach: https://github.com/potetm/fusebox/blob/0d3605442f2c5e812284678f69924c5fd6588179/src/clojure/com/potetm/fusebox.clj#L17-L44
though I later abandoned it in favor of just using millis for everything (this was time only), so ymmv
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?
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]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.
If you define dollars as (def $ (units/* 1 1)), $ will bind to the number 1! So not a Munit base unit.
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
(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)))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
why is ::impl being treated as inline to the :hook-impls part of s/cat?
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
oh, i need :hook-impls (spec/+ (spec/spec ::impl)) because i'm starting a new nested context
there is a spec for protocols in a jira if you wanted to swipe that btw
regex specs all combine into one "nesting level", s/spec can be used to create a new one
well, maybe there isn't a jira for that, can't find it now. Sure thought I had worked on it at some point
there's one for extend-protocol but i don't think there's an attached patch: https://clojure.atlassian.net/browse/CLJ-2836
there's no doubt other tickets i don't remember/know about