This document describes practical contracts (what is guaranteed, what is "soft" vs
"strict", how the default registry is chosen, when exceptions are thrown, how the
protocols behave) for Bankster's core axis: Currency, Money, Registry records
and the Monetary, Scalable and Accountable protocols.
This is not an API reference (it does not list all arities), but a guide to behavior.
Money = (Currency, BigDecimal amount).Registry is the source of truth for currencies (resolving by ID/code/numeric-id,
localization, country relations).BigDecimal (no silent double).nil when no match is found; the "strict" API throws
ExceptionInfo.io.randomseed.bankster.currency auto-initializes the global registry from
config.edn by default. To disable this side effect (and initialize explicitly),
bind io.randomseed.bankster/*initialize-registry* to false around require.io.randomseed.bankster.jsr-354 is an experimental, work-in-progress
Clojure semantic bridge inspired by JSR-354 (JavaMoney). It is not a Java
implementation/interface of the standard. The goal is to progressively cover more
of JSR-354 semantics in future Bankster releases; until then, treat this namespace
as unstable.:weight is a registry attribute used to resolve conflicts when resolving by
code and numeric ID (in :cur-code->curs and :cur-nr->curs). Weight is stored
in the registry base map :cur-id->weight (exported as top-level :weights in
EDN). Currency instances stored in registries also carry the weight in metadata as
an optimization. Weight is ignored by Currency/Money equality and arithmetic.:EUR or :crypto/ETH (namespaced).:EUR, :ETH (from :crypto/ETH
you get :ETH).:ISO-4217, :CRYPTO). For
namespaced currencies it is derived from the namespace (upper-case).:iso/fiat, :virtual/token).Money semantics.Money: the scale of the amount (BigDecimal scale).clojure.lang.ExceptionInfo (ex-info).ex-data you will typically find keys like :registry, :currency, :value,
:op, and/or domain-specific keys (:augend, :addend, :minuend,
:subtrahend, :dividend, :divisor).BigDecimal arithmetic (Java ArithmeticException),
Bankster rethrows it as ExceptionInfo and marks it in ex-data with:
:arithmetic-exception true,:arithmetic-exception/cause (the original ArithmeticException, also used as
the exception cause).io.randomseed.bankster/CurrencyFields:
:id (keyword) - unique currency identifier within a registry.:numeric (long) - numeric id (e.g. ISO 4217); absence is represented by a
sentinel (no-numeric-id).:scale (int) - nominal currency scale; auto-scale is represented by a sentinel
(auto-scaled).:kind (keyword or nil) - classification (case-sensitive; may be namespaced, e.g.
:iso/fiat, :virtual/token).:domain (keyword or nil) - domain (e.g. :ISO-4217, :CRYPTO).Non-inherent attribute:
:weight (int) - weight used to resolve conflicts when resolving by code and/or
numeric ID (lower wins).
:cur-id->weight (:weights in EDN config).Currency instances carry weight in metadata for hot paths
(bucket sorting), accessible via currency/weight.:weight may be omitted (implicit 0).importer/registry->map) emits weights under top-level
:weights (presence is meaningful; explicit 0 is supported).:weight into :currencies while
keeping orphaned :weights entries.Contracts:
Currency is "data first": field values are explicit; there are no hidden side
effects.(.toString Currency) returns the code (i.e. (name :id)), not the full ID. Do
not rely on toString in logs. For stable identification use currency/id
(keyword) or currency/to-id-str (string without keyword interning).Currency values may carry an extension map (extra keys) because they are Clojure
records. Bankster itself keeps core semantics in record fields and treats
extension keys as non-semantic metadata.
Currency objects, use
:propagate-keys (global allowlist) and/or per-currency :propagate-keys
(override). Keys reserved by core currency construction and configuration
pre-population (e.g. :id, :kind, :scale, :numeric, :domain, :weight,
:countries, :localized, :traits, and the directive :propagate-keys) are
never propagated.io.randomseed.bankster/MoneyFields:
:currency - a Currency object.:amount - java.math.BigDecimal.Contracts:
Money always carries a BigDecimal (inputs are coerced through the scale
layer).(.toString Money) returns "AMOUNT CODE" (code comes from Currency/toString).Money is part of the data (it may differ from the currency's
nominal scale). This is supported, but has consequences (see money/rescaled?,
money/rescale, money/strip).io.randomseed.bankster/RegistryThe registry is a record with indices (maps), including:
:cur-id->cur - currency ID -> Currency (canonical entry).:cur-code->curs - currency code -> a set of currencies (sorted, "weighted").:cur-nr->cur - numeric-id -> Currency.:cur-nr->curs - numeric-id -> a set of currencies (sorted, "weighted").:ctr-id->cur - country ID -> Currency.:cur-id->ctr-ids - currency ID -> a set of country IDs.:cur-id->localized - currency ID -> localization/properties map.:cur-id->traits - currency ID -> traits (a set/vector of keywords; advisory).:cur-id->weight - currency ID -> int weight (conflict resolution).:hierarchies - a CurrencyHierarchies record holding per-axis hierarchies
(usually at least :domain and :kind, optionally :traits and/or custom axes).:version - string (timestamp-style).:ext - extra data (map).Contracts:
io.randomseed.bankster.registry/R (Atom).registry/*default*
(when bound), otherwise the global registry/R.isa? (e.g.
currency/of-domain?, currency/of-kind?, currency/of-trait?). Custom axes may be introduced by
consumers (stored under additional keys in :hierarchies).:cur-nr->curs and picks a canonical one for :cur-nr->cur using weight (lower
wins).io.randomseed.bankster.currency/MonetaryMonetary is the "coercion + currency resolution" layer.
General rule:
to-* methods are cheap and registry-free (may return nil).resolve/resolve-all consult a registry and are "soft" (may return nil).unit/of-id are "strict" (throw when the currency is missing from the registry),
with an explicit convention registry=nil for already constructed Currency
values (see below).Key methods and their contracts:
to-id -> keyword:
to-code -> keyword:
to-id-str / to-code-str -> String:
to-numeric-id -> long/nil:
to-currency -> Currency/nil:
Currency (e.g. from a map).to-map -> map/nil:
definitive? -> boolean:
resolve -> Currency/nil:
nil when it cannot be resolved.registry is nil, uses the default registry.resolve-all -> set/nil:
nil.id -> keyword:
:BTC -> :crypto/BTC when such currency exists),registry=nil means "do not consult a registry" (return local
ID/coercion).Currency values the registry is ignored and .id is
returned.registry should be a Registry (or nil as above). The boolean
sentinel true works only syntactically via (registry/get true) (macro-level);
passing true as a runtime value is not supported.id always consults a registry and throws when
the mapping is missing.of-id -> Currency:
Currency: registry=nil means "return as-is".unit -> Currency:
Currency: registry=nil means "return as-is",defined? -> boolean:
present? -> boolean:
Soft helpers:
currency/attempt and currency/attempt*:
Currency or nil, does not throw.io.randomseed.bankster.scale/ScalableScalable is the "what is scale" + "how to safely produce BigDecimal" layer.
Methods:
scalable? -> boolean: can this value be coerced to a scalable value?applied? -> boolean: does the value already carry scale information (e.g.
BigDecimal, Money, Currency)?of -> long: scale (for Money it's the amount scale; for Currency it's the
currency scale).apply -> scaled value:
BigDecimal,Money: may rescale the amount; unary apply reapplies the currency's
nominal scale (when the currency is not auto-scaled).Currency: returns a Currency with updated :scale.amount -> BigDecimal:
Money: returns the amount,Currency: returns nil (a currency does not have an "amount"),BigDecimal (after coercion).Dynamic vars and rounding:
scale/*rounding-mode*:
scale/*each*:
scale/with-rounding:
*rounding-mode* and sets a thread-local fast path for rounding-mode
lookups (performance).scale/with-rescaling:
*each* + *rounding-mode* and sets a thread-local fast path for
rounding-mode lookups (performance).Recommendation:
scale/with-rounding / scale/with-rescaling (or the aliases in
io.randomseed.bankster.money) instead of a raw binding on
scale/*rounding-mode* in performance-sensitive code. Plain binding remains
supported, but it does not use the fast path and may be noticeably slower in
tight numeric loops.io.randomseed.bankster.money/AccountableAccountable is the "what can become Money" + "how to convert Money across currencies/registries" layer.
Methods:
value -> Money/nil:
Money from numbers/strings/currency identifiers, etc.,nil it typically returns nil,scale/*rounding-mode* is not set.cast -> Money:
money/of-registry.io.randomseed.bankster.registry)Creation and global state:
registry/new, registry/new-registry - create a new registry.registry/R - Atom holding the global registry.registry/get - returns a registry:
(registry/get) uses the default registry (dynamic or global),(registry/get registry) prefers the provided registry unless it is nil or false,(registry/get true) works as a syntactic sentinel (macro-level): use the default registry.registry/state - @R (global).registry/set! - sets the global registry.registry/update, registry/update! - functional / global update.registry/with - lexically binds the default registry (registry/*default*).registry/hierarchies, registry/hierarchy - access registry hierarchies.registry/hierarchy-derive, registry/hierarchy-derive! - update a selected
hierarchy axis in a registry (pure / global mutation).Read-only indices:
registry/currency-id->currency, registry/currency-code->currencies,
registry/currency-nr->currency, etc.registry/currency-id->traits - access per-currency traits.registry/version, registry/ext.Diagnostics:
registry/*warn-on-inconsistency*, registry/*warnings-logger*,
registry/inconsistency-warning.io.randomseed.bankster.currency)Construction:
currency/new-currency, currency/new, currency/map->new:
ISO-4217 which
is stripped),:domain as :ISO-4217 under typical conditions,:weight defaults to 0.Default currency / registry:
currency/*default*, currency/set-default!, currency/unset-default!.currency/set-default-registry!, currency/config->registry.Resolution and coercion:
currency/of (macro) - convenient currency retrieval (from registry or ad-hoc for
maps).currency/unit, currency/of-id - strict; throw when currency is missing from
the registry.currency/resolve, currency/resolve-all - soft; return nil when nothing
matches.currency/attempt, currency/attempt*, currency/with-attempt - soft helpers.Registry operations (mutate a registry value functionally; no side effects unless you
use ! variants):
currency/register, currency/unregister, currency/update (+ ! variants on
the global registry).currency/add-countries, currency/remove-countries (+ !).currency/add-localized-properties, currency/remove-localized-properties (+ !).currency/add-weighted-code (associate code with currency; conflicts resolved by
weight).currency/set-weight, currency/clear-weight (+ !) - narrow API to mutate the
registry weight base (:cur-id->weight) while keeping weighted indices and
canonical numeric resolution synchronized.currency/set-traits, currency/add-traits, currency/remove-traits (+ !) -
narrow API to mutate the registry traits base (:cur-id->traits).Predicates and classification:
currency/currency?, currency/possible?, currency/definitive?,
currency/defined?, currency/present?.currency/iso?, currency/iso-strict?, currency/iso-legacy?, currency/crypto?,
currency/fiat?, etc.currency/of-domain? - hierarchy-aware domain predicate (uses registry/:hierarchies when present).currency/of-kind? - hierarchy-aware kind predicate (uses registry/:hierarchies when present).currency/has-trait? (exact membership) and currency/of-trait? (hierarchy-aware) - trait predicates
backed by registry/:cur-id->traits and optionally registry/:hierarchies/:traits.currency/null?, currency/none? - nil/empty/null-currency helpers.currency/same-ids? - identity comparison by ID (soft, symmetric).Properties and localization:
currency/id, currency/code, currency/ns-code, currency/nr, currency/sc,
currency/domain, currency/kind, currency/weight.currency/info - full currency info map (fields + registry metadata).currency/countries, currency/localized-properties, currency/localized-property.currency/symbol, currency/display-name (+ *-native).currency/formatter, currency/formatter-extended.Tagged literals:
#currency ... (via currency/code-literal / currency/data-literal).io.randomseed.bankster.money)Creation / parsing:
money/value (Accountable) - the primary constructor function.money/of, money/of-major, money/of-minor (macro) - ergonomic creation.money/major-value, money/minor-value - creation via major/minor parts.money/parse, money/parse-major, money/parse-minor - internal parsers (public,
but mostly low-level).money/of-registry - forces the currency in Money to come from the given registry
(and aligns scale).Properties and inspection:
money/amount, money/currency, money/stripped-amount, money/unparse.money/money?, money/rescaled?, money/auto-scaled?.Comparisons and predicates:
money/compare, money/compare-amounts (strict: require matching currency; nil
is comparable and is the "lowest").money/eq?, money/eq-am? (==), money/ne?, money/ne-am? (not==).money/gt?, money/ge?, money/lt?, money/le?.money/is-pos?, money/is-neg?, money/is-zero?, money/is-pos-or-zero?,
money/is-neg-or-zero? (+ aliases pos?, neg?, zero?).Arithmetic (general rule: currencies must be "the same currency", but weight is ignored):
money/add (+), money/sub (-):
Money and require matching currency,Money.money/mul (*):
Money * numbers,Money argument in the whole expression; otherwise throws
ExceptionInfo.money/div (/):
Money / number -> Money,Money / Money (same currency) -> BigDecimal,number / Money -> exception.money/div works like clojure.core// for numbers, but throws for Money
(because it would be equivalent to number / Money).money/rem:
div: for Money/Money the result is BigDecimal, for
Money/number the result is Money.Scaling / rounding:
money/scale, money/rescale, money/round, money/round-to, money/strip (use
consciously).money/with-rounding, money/with-rescaling (aliases to
scale/...).Allocation:
money/allocate:
money/distribute:
allocate with ratios (repeat n 1).Formatting:
money/format, money/format-with (use formatters from currency).Tagged literals / readers:
#money ... (via money/code-literal / money/data-literal + *-crypto variants).money/data-readers, money/code-readers.io.randomseed.bankster.money.ops:
+, -, *, /, =, etc.) that always mean Money semantics.io.randomseed.bankster.money.inter-ops:
Money argument, behaves 1:1 like clojure.core,Money argument, it "taints" the operation and switches to
Bankster semantics.toString. Use currency/id or currency/to-id-str.clojure.core/= for Money comparisons (it uses record/map equality, compares
BigDecimal values using Clojure numeric equality (scale-insensitive), and may
reflect non-semantic differences like currency extension keys). Prefer money/eq?
(or money/=) / money/eq-am? (money/==), or the inter-op layer
io.randomseed.bankster.money.inter-ops/= for mixed numeric expressions.to-id-str / to-code-str and
validate, instead of calling keyword (interning).scale/*rounding-mode* via scale/with-rounding
or pass rounding-mode explicitly.M, orCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |