Money is an immutable value object representing an amount together with its currency unit. Its identity comes from those values: changing either creates a different Money entity. section 4.4.2 section 4.5
A Money object typically provides:
- Readable fields such as amount and currency. section 4.5
- Value comparison through equals and numeric ordering through compareTo. section 4.5
- Arithmetic that returns new values: plus, minus, times, rounding, and negation. section 4.5
- Conversion and display hooks through valueOf and toString. section 4.5
Listing 4.13 Details of the Money value object
const BTC = 'B|';
const Money = curry((currency, amount) =>
compose( #1
Object.seal,
Object.freeze
)(Object.assign(Object.create(null), #2
{
amount,
currency,
equals: other => currency === other.currency &&
amount === other.amount,
round: (precision = 2) =>
Money(currency, precisionRound(amount, precision)),
minus: m => Money(currency, amount - m.amount),
plus: m => Money(currency, amount + m.amount),
times: m => Money(currency, amount * m.amount),
compareTo: other => amount - other.amount,
asNegative: () => Money(currency, amount * -1),
valueOf: () => precisionRound(amount, 2), #3
toString: () => `${currency}${amount}`
}
))
);
// Zero
Money.zero = (currency = BTC) => Money(currency, 0); #4
// Static helper functions
Money.sum = (m1, m2) => m1.add(m2);
Money.subtract = (m1, m2) => m1.minus(m2);
Money.multiply = (m1, m2) => m1.times(m2);
function precisionRound(number, precision) {
const factor = Math.pow(10, precision);
return Math.round(number * factor) / factor;
}
The factory curries currency and amount, builds a null-prototype object, adds value-oriented operations, then freezes and seals the result. section 4.5
Currency-specific construction
const USD = Money('$'); USD(3.0).amount; // 3 USD(7.0).currency; // $ USD(3.0).plus(USD(7.0)).equals(USD(10)); // true Money.zero('$'); section 4.5
Mutation fails
const threeDollars = USD(3.0); threeDollars.amount = 5; // TypeError: Cannot assign to read only property 'amount' Object.isFrozen(threeDollars); // true section 4.5
Symbol.toStringTag can label instances as Money(currency amount), making errors from attempted mutation easier to identify during debugging. section 7.5.1
