Measurement in Swift: Simplified

·

2 min read

I have always liked the idea of using explicit Measurement values (magnitude and unit) but I have found them cumbersome in actual code. There are a few solutions out there but I found them somehow unsatisfactory.

So, after some research and reflection, I hit on the following as a desirable syntax that nicely supports autocomplete from the available units for any typed Dimension.

let m: Mass = 123(.kilograms) // => 123.0 kg
let m2: Mass = 123(.kilograms) + 17(.stones) // => 230.95493 kg
m2.converted(to: .pounds) // 509.1688786398349 lb

And with a little thought and experimentation, I was able to implement this quite succinctly.

public typealias Mass = Measurement<UnitMass>

public extension Double { 
  func callAsFunction <U: Dimension>(_ units: U) -> Measurement<U> {
        Measurement(value: self, unit: units)
    }
}

public extension Int {
  func callAsFunction <U: Dimension>(_ units: U) -> Measurement<U> {
        Measurement(value: Double(self), unit: units)
    }
}

Add one small extension for the an implicitly unitized zero value.

// Example
let z: Mass = .zero

// Using
public extension Measurement where UnitType: Dimension {
    static var zero: Measurement<UnitType> {
        Measurement(value: 0, unit: UnitType.baseUnit())
    }
}

By using generics in the Double and Int extensions, we can easily include any other Measurement Units with no additional tedious boilerplate code to write.

public typealias Duration = Measurement<UnitDuration>
public typealias Angle = Measurement<UnitAngle>
public typealias Length = Measurement<UnitLength>
public typealias Speed = Measurement<UnitSpeed>

If you find you want or need any additional units for a particular Dimension they are easy enough to add as extension.

// As an example
extension UnitDuration {
    static let SecsPerDay: Double = 86_400
    static let days = UnitDuration(symbol: "days", 
         converter: UnitConverterLinear(coefficient: SecsPerDay))

    static let weeks = UnitDuration(symbol: "weeks",
         converter: UnitConverterLinear(coefficient: SecsPerDay * 7))
    static let months = UnitDuration(symbol: "months",
         converter: UnitConverterLinear(coefficient: SecsPerDay * 30))

    static let years = UnitDuration(symbol: "years",
         converter: UnitConverterLinear(coefficient: SecsPerDay * 365))
}

If this is useful take a look here for the complete code along with some other useful additions.

If you are interested in learning more about Units and Measurements and how to create your custom ones I recommend the following.