Introducing Money

Introducing Money

Money is a Swift framework which I have open sourced today. In this post I will discuss a little bit about how it works, and what it can be used for.

Decimals

Working with money is one of those situations where precision is the most important aspect. For such situations, Apple have provided NSDecimalNumber and NSDecimal. Both are immutable types, but the former is a class, the later a "C style" struct. Both types are able to represent numbers in base 10 with up to 38 significant digits. That's quite precise.

When performing arithmetic with these types, defining the decimal behavior is also important. The behavior defined via the protocol NSDecimalNumberBehaviors specifies how the number should be rounded, its scale and when to throw exceptions. The default behavior would be to use a plain rounding mode, a scale of 38 (significant digits), and to only throw exceptions when dividing by zero.

These types can provide the nitty gritty details of a possible Money type. However, they have no way of specifying their own intrinsic decimal behavior. What we want is a generic type which defines its own rounding style.

Therefore, we need a protocol for the behavior.

public protocol DecimalNumberBehaviorType {

    /// Specify the decimal number (i.e. rounding, scale etc) for base 10 calculations
    static var decimalNumberBehaviors: NSDecimalNumberBehaviors { get }
}

We will also need a protocol to define decimal number types.

/**
 # DecimalNumberType
 A protocol which defines the necessary interface to support decimal number
 calculations and operators.
 */
public protocol DecimalNumberType: SignedNumberType, IntegerLiteralConvertible, FloatLiteralConvertible, CustomStringConvertible {

    typealias DecimalStorageType
    typealias DecimalNumberBehavior: DecimalNumberBehaviorType

    /// Access the underlying storage
    var storage: DecimalStorageType { get }

    /**
     Initialize a new `DecimalNumberType` with the underlying storage.
     This is necessary in order to convert between different decimal number
     types.
     
     - parameter storage: the underlying decimal storage type
     e.g. NSDecimalNumber or NSDecimal
     */
    init(storage: DecimalStorageType)

    /**
     Add a matching `DecimalNumberType` to the receiver.
     
     - parameter other: another instance of this type.
     - returns: another instance of this type.
     */
    @warn_unused_result
    func add(_: Self) -> Self

    // etc includes API for subtract, multiple, divide, remainder, negate etc
}

Given this protocol, we can now implement the arithmetic operators such as + and - for all decimal number types. For example:

@warn_unused_result
public func +<T: DecimalNumberType>(lhs: T, rhs: T) -> T {
    return lhs.add(rhs)
}

A subtle point about the above protocol, is that the underlying decimal storage is not defined. We're going to create a decimal number type which uses NSDecimalNumber, but it could also be NSDecimal.

We can create a decimal type which will use NSDecimalNumber but is still generic over DecimalNumberBehaviorType.

/**
 # Decimal
 A value type which implements `DecimalNumberType` using `NSDecimalNumber` internally.
 
 It is generic over the decimal number behavior type, which defines the rounding
 and scale rules for base 10 decimal arithmetic.
*/
public struct _Decimal<Behavior: DecimalNumberBehaviorType>: DecimalNumberType {
    public typealias DecimalNumberBehavior = Behavior

    /// Access the underlying decimal storage.
    /// - returns: the `NSDecimalNumber`
    public let storage: NSDecimalNumber
    
    // etc etc

}

The reason the type is named _Decimal is because we can define a typealias for the default "plain" rounding behavior.

/// `Decimal` with plain decimal number behavior
public typealias Decimal = _Decimal<DecimalNumberBehavior.Plain>

Using a typealias like this is new in Swift 2, and it's probably my most favorite feature, well, tied with protocol extensions. It's certainly incredibly handy and not often mentioned.

This complete the Swiftism of decimals. But, how does this help us with money?

MoneyType

I discovered (after writing this framework) that Martin Fowler has written about the lack of a money type in software languages. He writes that money should be a "first class data type", with the "lack of types causing problems especially for currencies". And of course, he's absolutely correct.

In his Money implementation, when performing maths he asserts that the composed Currency type of the operands match. His book seems to gloss over the details of the Currency type (at least I couldn't find it), and I expect that it uses string comparison. One problem with this is that it will blow up during runtime, and not at compile time. We'll take the Swift generic approach, and create a MoneyType protocol which is generic over a CurrencyType. This way, if we try to add dollars to pounds, the code won't compile.

First we need a protocol for the CurrencyType:

public protocol CurrencyType: DecimalNumberBehaviorType {

    /// The currency code
    static var code: String { get }

    // etc
}

In a protocol extension we can implement the decimalNumberBehavior property required by DecimalNumberBehaviorType. The MoneyType protocol uses this to constrain it's nested Currency type.

/**
 # MoneyType
 `MoneyType` is a protocol which refines `DecimalNumberType`. It
 adds a generic type for the currency.
*/
public protocol MoneyType: DecimalNumberType {
    typealias Currency: CurrencyType
}

Now finally, we can define _Money which implements MoneyType.

public struct _Money<C: CurrencyType>: MoneyType {
    
    public typealias DecimalNumberBehavior = C
    public typealias Currency = C

    /// Access the underlying decimal.
    public let decimal: _Decimal<C>

    // etc etc - not shown implementation of DecimalNumberType
}

Okay, so this is sorta neat. But, to actually use it, we need to have some currency types.

As part of the framework's build process, it runs a Swift script which generates another Swift file which is compiled into the framework. This auto-generated Swift file, contains currency types for all 298 ISO currency codes. Essentially the script just iterates over the codes returned from NSLocale.ISOCurrencyCodes() and prints out currency types. In addition to creating the currency types, the script also generates type aliases for all the currencies. For example:

public typealias JPY = _Money<Currency.JPY>

let yen: JPY = 2_000

Which means that effectively, we have USD, and GBP and EUR as proper "first class" value types. You can perform mathematical operations on values of the same type (and with Int, Double and NSDecimalNumber). But code which adds USD to EUR will fail to compile.

Additionally, the framework defines a special Currency.Local currency, which is the currency of the device's (iPhone, Mac, Apple TV) current locale. And the icing on the cake is

/// The current locale money
public typealias Money = _Money<Currency.Local>

Foreign Exchange

Of course, what sort of Money framework would this be if it didn't support foreign exchanges (FX)? Obviously in real life we can exchange dollars for pounds etc. DecimalNumberType exposes the underlying storage so that numbers with the same storage type can be converted. Therefore, the role of an FX service is just to provide the exchange rate, as a BankersDecimal. But we go one step further, and that is to make the FXProviderType (the protocol which defines FX service providers) generic over the two currencies. The framework includes a provider which wraps the free Yahoo Currency Converter. It works like this:

Yahoo<USD,EUR>.quote(100) { result in
    if let (dollars, quote, euros) = result.value {
        print("Exchanged \(dollars) into \(euros) with a rate of \(quote.rate)")
    }
}

Exchanged US$ 100.00 into € 92.15 with a rate of 0.9215

There are a couple of things to note here. Firstly, the foreign exchange isn't actually converting any money. These are just numbers in a computer program. But they might represent real money in your application.

The provider is Yahoo<USD,EUR>. All the information needed to perform the exchange is available thanks to MoneyType and CurrencyType. There is no need to instantiate any values, instead we can access type information from generic protocols.

The quote static function isn't defined in the Yahoo generic type. Instead it is provided in an extension on FXRemoteProviderType, the underlying protocol. All the provider needs to do is create the network request object, and provide a function to map the network response into a quote object. The actual networking and conversion bits can be done by the framework, leaving less code for framework consumers to write, test and worry about.

Custom Currencies

Lastly, I'll just mention custom currencies. It's quite simple to create your own types which implement CurrencyType. I think this will be very useful for games which have their own in-app currency system. E.g. coins, gold rings, green gems, joo joo beans etc. There is an example project which creates two custom currencies with a Bank which acts as local (i.e. synchronous) currency exchange.

If your app allows the user to purchase in-app currency (as most top grossing games do), then it would make sense to create an FX provider for this. Instead of going to Yahoo for a rate, it would allow the user to purchase quantities of your currencies. The framework doesn't yet support this - but I'm working on it. Stay tuned!

Anyway, I think that covers the details. Checkout the code on GitHub. For Swift learners the framework is a great source of examples of generics, protocols, protocol extensions, operators, system architecture and testing. It has 100% test coverage, and the tests of the Yahoo FX provider (which uses DVR) show how to unit test static functions on generic types.