How do I return an unspecified Measurement<T> from a function?

282 Views Asked by At

UPDATE: Okay, I'm confused. My original code did not compile. At some point in cutting it down to a minimal example, it apparently started working. I have rewritten the original version enough now that I've lost track of why it didn't work in the first place. Going to just mark this question resolved.


I am trying to figure out how to write a function that returns an arbitrary Measurement, without having to specify in advance what UnitType the Measurement has. The following is more or less what I want to do, but it does not compile. (The arguments to the function don't matter.)

   func getValue(for x:Int) -> Measurement<Unit>? {
        if x==1 {
            return Measurement(value:5, unit:UnitSpeed.metersPerSecond)
        } else {
            return Measurement(value:2, unit:UnitLength.meters)
        }
    }

I don't want to have to care what the Unit is! Measurements encapsulate their unit, and all I want to do is plug it into something like: Text(reading?.unit.symbol ?? "") at the other end.

func getValue(for measure:TrackedMeasure) -> some Measurement? {} complains "Reference to generic type 'Measurement' requires arguments in <…>"

The suggested fix is func getValue(for measure:TrackedMeasure) -> some Measurement<Unit>? { }, which then complains "An 'opaque' type must specify only 'Any', 'AnyObject', protocols, and/or a base class"

There does not seem to be a protocol for Measurement, and I can't figure out how to define one.

Do I have to, like, reimplement Measurement to make this work?

2

There are 2 best solutions below

2
On BEST ANSWER

Measurement<UnitType> is a generic type and it does not extend a common base class or conform to a common protocol, so a concrete type Measurement<UnitLength> is a different type than Measurement<UnitSpeed>.

The only way around it is to do something like a type erasure, and have them both conform to a common protocol AnyMeasurement:

protocol AnyMeasurement {
  var baseUnit: Unit { get }
  var value: Double { get }
}

Then you can conform Measurement to AnyMeasurement:

extension Measurement: AnyMeasurement {
  // just need to return a base Unit, instead of a concrete UnitType
  // value: Double { get } is already implemented by Measurement
  var baseUnit: { self.unit }
}

Then, you could do something like this:

func randomMeasurement() -> AnyMeasurement {
   let speed  = Measurement(value: 10, unit: UnitSpeed.metersPerSecond)
   let length = Measurement(value: 10, unit: UnitLength.meters)

   return Bool.random() ? speed : length
}

print(randomMeasurement().baseUnit.symbol)
1
On
enum TrackMeasure {
    case speed
    case distance
    case time
}

func getValue(for measure: TrackMeasure) -> Measurement<Unit> {

    switch measure {
        case .speed:
            return Measurement(value: 60, unit: UnitSpeed.milesPerHour)
        case .distance:
            return Measurement(value: 10, unit: UnitLength.miles)
        case .time:
            return Measurement(value: 6, unit: UnitDuration.minutes)
    }
}

let speed = getValue(for: .speed)
let distance = getValue(for: .distance)
let time = getValue(for: .time)