money.add
Add, subtract and negate monetary amounts, refusing to mix currencies.
1.0.1 · published 2026-10-03 by charlie · Anterra
Pinned by 12 tests, run in TypeScript, Python and Rust.
What it does
Also exports subtractMoney and negateMoney: they are the same group operation and splitting them across three packages would only make them easier to get wrong separately.
1.0.1 adds tests; behaviour unchanged.
For example
add_money(£19.99, £0.01)→ £20.00 adds penceadd_money(£25.00, £0.00)→ £25.00 adds zeroadd_money(£25.00, -£10.00)→ £15.00 adds a negative (a credit)
The function
The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.
def add_money(a: Money, b: Money) -> Money
| a | Money | |
| b | Money | |
| returns | Money |
Your code names it in one line, in the file that uses it
from fune.money.add import add_money # money.add@^1
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
from .money_amount import Money, assert_same_currency, money ← from money.amount ^1.0.0 · built alongside by fune
def add_money(a: Money, b: Money) -> Money:
assert_same_currency(a, b)
return money(a.minor + b.minor, a.currency)
def subtract_money(a: Money, b: Money) -> Money:
assert_same_currency(a, b)
return money(a.minor - b.minor, a.currency)
def negate_money(a: Money) -> Money:
return money(-a.minor, a.currency)Install
fune build
With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:
fune add money.add
The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./money.add-1.0.1-python.fune, or fetch it from a terminal with fune pull money.add@1.0.1:python.
The whole function, every language, is one file too: money.add-1.0.1.fune, 5,558 bytes, sha256 8cd9bf58cd000b7479fa5e8e95e7e9aaf8742f7615506f2c78c2793ebbe8bd69. It installs into a project of any language.
Customise it in your app
The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.
before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.
# fune: before money.add
after — your function gets the result and the arguments, and returns the final result.
# fune: after money.add
replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.
# fune: replace money.amount in money.add
step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show money.add --steps.
# fune: step money.add after <n|label>
Tests
A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.
| Case | Arguments | Expected | |
|---|---|---|---|
| adds pence | £19.99, £0.01 | → | £20.00 |
| adds zero | £25.00, £0.00 | → | £25.00 |
| adds a negative (a credit) | £25.00, -£10.00 | → | £15.00 |
| mixing currencies is an error | £1.00, €1.00 | → | error: currency mismatch |
| a credit larger than the amount goes below zero | £1.00, -£2.50 | → | -£1.50 |
| two credits add up to a bigger credit | -£3.00, -£2.00 | → | -£5.00 |
| a credit that cancels the amount exactly is zero | £25.00, -£25.00 | → | £0.00 |
| zero-decimal currency | ¥1,500, ¥2,500 | → | ¥4,000 |
| up to the largest safe integer, exactly | £90,071,992,547,409.90, £0.01 | → | £90,071,992,547,409.91 |
| mixed currencies are refused even when both are zero | £0.00, €0.00 | → | error: currency mismatch |
Show the other 2 tests
| Case | Arguments | Expected | |
|---|---|---|---|
| currency codes are compared exactly, so GBP and gbp do not mix | £1.00, 1.00 gbp | → | error: currency mismatch |
| matching but invalid currencies are still refused | 0.01 gbp, 0.01 gbp | → | error: ISO 4217 |
Files
| Path | Bytes |
|---|---|
| README.md | 225 |
| impl/python.py | 394 |
| impl/rust.rs | 767 |
| impl/typescript.ts | 432 |
| vectors.json | 2,196 |