Skip to content

Commit

Permalink
Rename "Money" to "AnyCurrency"
Browse files Browse the repository at this point in the history
Motivation:

Having a type named "Money" in a module named "Currency" with other types using the term "currency" made it confusing to have similar terms.

"Money" also did not properly reflect that it is a type-erasure protocol, while "AnyCurrency" provides a stronger indication at use sites.

Modifications:

- Rename `Money` to `AnyCurrency`
- Update code documentation to be less brittle and reflect the new naming
- Rename `_MoneyUmbrellaProtocol` to `_CurrencyImplementation`

Result:

Naming schemes in swift-currency should be more consistent and helpful to those learning the library's API.
  • Loading branch information
Mordil committed Jan 15, 2020
1 parent cb6ecbb commit 8d9b01e
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 377 deletions.
50 changes: 25 additions & 25 deletions Sources/Currency/Money.swift → Sources/Currency/AnyCurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import Foundation

/// A generic representation of money, with a single `Foundation.Decimal` value holding the exact value of money.
/// A type-erased representation of a currency, with a single `Foundation.Decimal` holding the exact value.
///
/// Conforming types will also provide `CurrencyMetadata` to access specific ISO 4217 information, such as the alphabetic code and "minor units".
///
Expand All @@ -26,9 +26,9 @@ import Foundation
///
/// Floating point values are notorious for having precision variance, even when they are equivalent within the precision range a developer desires.
///
/// To overcome this, all equality checks between two given Money values by default will use the `roundedAmount`.
public protocol Money {
/// The ISO 4217 information about this money's currency.
/// To overcome this, all equality checks between two given currencies will use the `roundedAmount` by default.
public protocol AnyCurrency {
/// The ISO 4217 information about this currency.
static var metadata: CurrencyMetadata.Type { get }

/// The exact amount of money being represented.
Expand Down Expand Up @@ -56,17 +56,17 @@ fileprivate func round(_ amount: Decimal, to scale: UInt8) -> Decimal {
return result
}

extension Money {
/// The ISO 4217 information about this money's currency.
extension AnyCurrency {
/// The ISO 4217 information about this currency.
public var metadata: CurrencyMetadata.Type { return Self.metadata }
public var roundedAmount: Decimal { return round(self.exactAmount, to: Self.metadata.minorUnits) }
}

extension Money where Self: CurrencyMetadata {
extension AnyCurrency where Self: CurrencyMetadata {
public static var metadata: CurrencyMetadata.Type { return Self.self }
}

extension Money {
extension AnyCurrency {
/// Initializes a currency value from it's smallest unit.
///
/// For example, the USD has cents, which are 1/100 of 1 USD. Calling `USD(minorUnits: 100)` will create a value of `1.0`.
Expand All @@ -84,7 +84,7 @@ extension Money {
// MARK: -
// MARK: Arithmetic

extension Money {
extension AnyCurrency {
public static func +(lhs: Self, rhs: Self) -> Self {
return .init(lhs.exactAmount + rhs.exactAmount)
}
Expand Down Expand Up @@ -130,8 +130,8 @@ extension Money {

// MARK: Equatable

extension Money {
public static func ==<M: Money>(lhs: Self, rhs: M) -> Bool {
extension AnyCurrency {
public static func ==<M: AnyCurrency>(lhs: Self, rhs: M) -> Bool {
guard Self.metadata.alphabeticCode == M.metadata.alphabeticCode else { return false }
return lhs.roundedAmount == rhs.roundedAmount
}
Expand All @@ -140,9 +140,9 @@ extension Money {
///
/// As floating point type precisions can vary, doing exact comparisons to `exactAmount` values can result in false negatives.
///
/// To get around this, the provided `other` amount will be rounded to the same precision as the "minorUnits" of the Money's currency using the "banker" mode.
/// To get around this, the provided `other` amount will be rounded to the same precision as the currency's "minorUnits" using the "bankers" mode.
///
/// See `Money.roundedAmount`.
/// See `AnyCurrency.roundedAmount` and `Foundation.Decimal.RoundingMode.bankers`.
/// - Parameter other: The other amount to compare against, after "bankers" rounding it.
/// - Returns: `true` if the rounded values are equal, otherwise `false`.
public func isEqual(to other: Decimal) -> Bool {
Expand All @@ -155,23 +155,23 @@ extension Money {
///
/// To get around this, the `roundedAmount` values will be compared.
///
/// See `Money.roundedAmount`.
/// - Parameter other: The other money to check if the rounded amounts are equal.
/// - Returns: `true` if the rounded amounts are equal, otherwise `false`.
public func isEqual<M: Money>(to other: M) -> Bool { return self == other }
/// See `AnyCurrency.roundedAmount`.
/// - Parameter other: The other currency value to check if the rounded amounts are equal.
/// - Returns: `true` if the currencies are the same type, and the rounded amounts are equal. Otherwise, `false`.
public func isEqual<M: AnyCurrency>(to other: M) -> Bool { return self == other }
}

// MARK: Comparable

extension Money {
extension AnyCurrency {
public static func <(lhs: Self, rhs: Self) -> Bool {
return lhs.exactAmount < rhs.exactAmount
}
}

// MARK: Hashable

extension Money {
extension AnyCurrency {
public var hashValue: Int { return self.exactAmount.hashValue }

public func hash(into hasher: inout Hasher) {
Expand All @@ -182,15 +182,15 @@ extension Money {
// MARK: -
// MARK: ExpressibleByIntegerLiteral

extension Money {
extension AnyCurrency {
public init(integerLiteral value: Int) {
self.init(Decimal(integerLiteral: value))
}
}

// MARK: ExpressibleByFloatLiteral

extension Money {
extension AnyCurrency {
public init(floatLiteral value: Double) {
self.init(Decimal(floatLiteral: value))
}
Expand All @@ -199,7 +199,7 @@ extension Money {
// MARK: -
// MARK: CustomStringCovertible

extension Money {
extension AnyCurrency {
public var description: String { return "\(self.roundedAmount.description) \(Self.metadata.alphabeticCode)" }
}

Expand All @@ -212,7 +212,7 @@ extension Money {

extension String.StringInterpolation {
public mutating func appendInterpolation(
_ money: Optional<Money>,
_ money: Optional<AnyCurrency>,
forLocale locale: Locale = .current,
nilDescription nilValue: String = "nil"
) {
Expand All @@ -223,7 +223,7 @@ extension String.StringInterpolation {
}

public mutating func appendInterpolation(
_ money: Money,
_ money: AnyCurrency,
forLocale locale: Locale = .current,
nilDescription nilValue: String = "nil"
) {
Expand All @@ -235,7 +235,7 @@ extension String.StringInterpolation {
}

public mutating func appendInterpolation(
_ money: Money,
_ money: AnyCurrency,
withFormatter formatter: NumberFormatter,
nilDescription nilValue: String = "nil"
) {
Expand Down
40 changes: 20 additions & 20 deletions Sources/Currency/CurrencyMint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,60 +18,60 @@ import struct Foundation.Decimal
public final class CurrencyMint {
public init() { }

/// Attempts to find the appropriate currency type that matches the provided code and intialize it.
/// Attempts to find the appropriate currency type that matches the provided alphabetic code and intialize it.
/// - Paramter code: The alphabetic ISO 4217 code to search for.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with a `.zero` amount. Otherwise `nil`.
public func make(alphabeticCode code: String) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with a `.zero` amount. Otherwise `nil`.
public func make(alphabeticCode code: String) -> AnyCurrency? {
return self.make(alphabeticCode: code, amount: .zero)
}

/// Attempts to find the appropriate currency type that matches the provided code and initialize it.
/// Attempts to find the appropriate currency type that matches the provided alphabetic code and initialize it.
/// - Parameters:
/// - code: The alphabetic ISO 4217 code to search for.
/// - amount: The amount the instance should be set to.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(alphabeticCode code: String, amount: Decimal) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(alphabeticCode code: String, amount: Decimal) -> AnyCurrency? {
guard let currencyType = CurrencyMint.lookup(byAlphaCode: code) else { return nil }
return currencyType.init(amount)
}

/// Attempts to find the appropriate currency type that matches the provided code and initialize it.
/// Attempts to find the appropriate currency type that matches the provided alphabetic code and initialize it.
///
/// See `CurrencyMetadata.minorUnits`.
/// - Parameters:
/// - code: The alphabetic ISO 4217 code to search for.
/// - minorUnits: The amount the instance should be set to, in the scale of it's smallest unit.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(alphabeticCode code: String, minorUnits: Int) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(alphabeticCode code: String, minorUnits: Int) -> AnyCurrency? {
guard let currencyType = CurrencyMint.lookup(byAlphaCode: code) else { return nil }
return currencyType.init(minorUnits: minorUnits)
}

/// Attempts to find the appropriate currency type that matches the provided code and initialize it.
/// Attempts to find the appropriate currency type that matches the provided numeric code and initialize it.
/// - Parameter code: The numeric ISO 4217 code to search for.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with a `.zero` amount. Otherwise `nil`.
public func make(numericCode code: UInt16) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with a `.zero` amount. Otherwise `nil`.
public func make(numericCode code: UInt16) -> AnyCurrency? {
return self.make(numericCode: code, amount: .zero)
}

/// Attempts to find the appropriate currency type that matches the provided code and initialize it.
/// Attempts to find the appropriate currency type that matches the provided numeric code and initialize it.
/// - Parameters:
/// - code: The numeric ISO 4217 code to search for.
/// - amount: The amount the instance should be set to.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(numericCode code: UInt16, amount: Decimal) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(numericCode code: UInt16, amount: Decimal) -> AnyCurrency? {
guard let currencyType = CurrencyMint.lookup(byNumCode: code) else { return nil }
return currencyType.init(amount)
}

/// Attempts to find the appropriate currency type that matches the provided code and initialize it.
/// Attempts to find the appropriate currency type that matches the provided numeric code and initialize it.
///
/// See `CurrencyMetadata.minorUnits`.
/// - Parameters:
/// - code: The numeric ISO 4217 code to search for.
/// - minorUnits: The amount the instance should be set to, in the scale of it's smallest unit.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(numericCode code: UInt16, minorUnits: Int) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(numericCode code: UInt16, minorUnits: Int) -> AnyCurrency? {
guard let currencyType = CurrencyMint.lookup(byNumCode: code) else { return nil }
return currencyType.init(minorUnits: minorUnits)
}
Expand All @@ -81,7 +81,7 @@ public final class CurrencyMint {


extension CurrencyMint {
fileprivate static func lookup(byAlphaCode code: String) -> Money.Type? {
fileprivate static func lookup(byAlphaCode code: String) -> AnyCurrency.Type? {
switch code {
case "AED": return AED.self
case "AFN": return AFN.self
Expand Down Expand Up @@ -245,7 +245,7 @@ extension CurrencyMint {
}
}

fileprivate static func lookup(byNumCode code: UInt16) -> Money.Type? {
fileprivate static func lookup(byNumCode code: UInt16) -> AnyCurrency.Type? {
switch code {
case 784: return AED.self
case 971: return AFN.self
Expand Down
40 changes: 20 additions & 20 deletions Sources/Currency/CurrencyMint.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -18,60 +18,60 @@ import struct Foundation.Decimal
public final class CurrencyMint {
public init() { }

/// Attempts to find the appropriate currency type that matches the provided code and intialize it.
/// Attempts to find the appropriate currency type that matches the provided alphabetic code and intialize it.
/// - Paramter code: The alphabetic ISO 4217 code to search for.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with a `.zero` amount. Otherwise `nil`.
public func make(alphabeticCode code: String) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with a `.zero` amount. Otherwise `nil`.
public func make(alphabeticCode code: String) -> AnyCurrency? {
return self.make(alphabeticCode: code, amount: .zero)
}

/// Attempts to find the appropriate currency type that matches the provided code and initialize it.
/// Attempts to find the appropriate currency type that matches the provided alphabetic code and initialize it.
/// - Parameters:
/// - code: The alphabetic ISO 4217 code to search for.
/// - amount: The amount the instance should be set to.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(alphabeticCode code: String, amount: Decimal) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(alphabeticCode code: String, amount: Decimal) -> AnyCurrency? {
guard let currencyType = CurrencyMint.lookup(byAlphaCode: code) else { return nil }
return currencyType.init(amount)
}

/// Attempts to find the appropriate currency type that matches the provided code and initialize it.
/// Attempts to find the appropriate currency type that matches the provided alphabetic code and initialize it.
///
/// See `CurrencyMetadata.minorUnits`.
/// - Parameters:
/// - code: The alphabetic ISO 4217 code to search for.
/// - minorUnits: The amount the instance should be set to, in the scale of it's smallest unit.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(alphabeticCode code: String, minorUnits: Int) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(alphabeticCode code: String, minorUnits: Int) -> AnyCurrency? {
guard let currencyType = CurrencyMint.lookup(byAlphaCode: code) else { return nil }
return currencyType.init(minorUnits: minorUnits)
}

/// Attempts to find the appropriate currency type that matches the provided code and initialize it.
/// Attempts to find the appropriate currency type that matches the provided numeric code and initialize it.
/// - Parameter code: The numeric ISO 4217 code to search for.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with a `.zero` amount. Otherwise `nil`.
public func make(numericCode code: UInt16) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with a `.zero` amount. Otherwise `nil`.
public func make(numericCode code: UInt16) -> AnyCurrency? {
return self.make(numericCode: code, amount: .zero)
}

/// Attempts to find the appropriate currency type that matches the provided code and initialize it.
/// Attempts to find the appropriate currency type that matches the provided numeric code and initialize it.
/// - Parameters:
/// - code: The numeric ISO 4217 code to search for.
/// - amount: The amount the instance should be set to.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(numericCode code: UInt16, amount: Decimal) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(numericCode code: UInt16, amount: Decimal) -> AnyCurrency? {
guard let currencyType = CurrencyMint.lookup(byNumCode: code) else { return nil }
return currencyType.init(amount)
}

/// Attempts to find the appropriate currency type that matches the provided code and initialize it.
/// Attempts to find the appropriate currency type that matches the provided numeric code and initialize it.
///
/// See `CurrencyMetadata.minorUnits`.
/// - Parameters:
/// - code: The numeric ISO 4217 code to search for.
/// - minorUnits: The amount the instance should be set to, in the scale of it's smallest unit.
/// - Returns: An instance of a `Money` type that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(numericCode code: UInt16, minorUnits: Int) -> Money? {
/// - Returns: An instance of a currency that matches the provided `code`, with the appropriate value. Otherwise `nil`.
public func make(numericCode code: UInt16, minorUnits: Int) -> AnyCurrency? {
guard let currencyType = CurrencyMint.lookup(byNumCode: code) else { return nil }
return currencyType.init(minorUnits: minorUnits)
}
Expand All @@ -85,7 +85,7 @@ public final class CurrencyMint {
% reader = csv.DictReader(file)

extension CurrencyMint {
fileprivate static func lookup(byAlphaCode code: String) -> Money.Type? {
fileprivate static func lookup(byAlphaCode code: String) -> AnyCurrency.Type? {
switch code {
% for row in reader:
% alphaCode = row["Ccy"]
Expand All @@ -97,7 +97,7 @@ extension CurrencyMint {
}
}

fileprivate static func lookup(byNumCode code: UInt16) -> Money.Type? {
fileprivate static func lookup(byNumCode code: UInt16) -> AnyCurrency.Type? {
switch code {
% file.seek(0)
% next(reader)
Expand Down
Loading

0 comments on commit 8d9b01e

Please sign in to comment.