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?
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)