Measurement in Swift: Simplified

Measurement in Swift: Simplified

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 SecondsPerDay: Double = 86_400

    static let days = UnitDuration(symbol: "days",
            converter: UnitConverterLinear(coefficient: SecondsPerDay))

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

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

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

If this 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 own custom ones I recommend the following.

Image Credit: Image by William Warby from unsplash .