Andrew
Andrew

Reputation: 551

How do I use Generics in a function call using Measurements in Swift 3

I need a function that works with the value in any Measurement in Swift 3. Below is an example:

var test2 = Measurement<UnitEnergy>( value: 10.1234, unit: .calories)

func printMeasurementGeneric( measurement: Measurement<Unit>) {
    print( measurement.value)
}

func printMeasurementEnergy( measurement: Measurement<UnitEnergy>) {
    print( measurement.value)
}

printMeasurementEnergy(measurement: test2) // This works
printMeasurementGeneric(measurement: test2)  // This doesn't work and give the following error

Playground execution failed: error: Measurement Playground.playground:136:38: error: cannot convert value of type 'Measurement<UnitEnergy>' to expected argument type 'Measurement<Unit>'
printMeasurementGeneric(measurement: test2)

What does the function need to look like for PrintMeasurementGeneric for this to work?

I've made some progress and now have the following code in Swift playground

var test = Measurement<UnitEnergy>( value: 10.1234, unit: .calories)

func printMeasurementGeneric<A:Dimension>( measurement: Measurement<A>) {
    print( measurement.value)
}

// This works
printMeasurementGeneric(measurement: test) // This works

// The following doesn't work
var objectValue: Any?
objectValue = test

var meas = objectValue as! Measurement<Dimension> // Could not cast value of type 'Foundation.Measurement<NSUnitEnergy>' (0x11d324028) to 'Foundation.Measurement<NSDimension>' (0x11d324088).
printMeasurementGeneric(measurement: meas)

I'm trying to use the objectValue in an NSControl to hold the Measurement. How do I do this within checking if it can be cast to every measurement type?

Some more work and including edits from below. Not a pretty or robust solution.

var test = Measurement<UnitEnergy>( value: 10.1234, unit: .calories)

func printMeasurementGeneric<A>( measurement: Measurement<A>) {
    print( measurement.value)
}

// This works
printMeasurementGeneric(measurement: test) // This works

// The following works but isn't pretty. Is there a better way
var objectValue: Any?
objectValue = test

if objectValue is Measurement<UnitFrequency> {
    let meas = objectValue as! Measurement<UnitFrequency>
    printMeasurementGeneric(measurement: meas)
} else if objectValue is Measurement<UnitLength> {
    let meas = objectValue as! Measurement<UnitLength>
    printMeasurementGeneric(measurement: meas)
} else if objectValue is Measurement<UnitSpeed> {
    let meas = objectValue as! Measurement<UnitSpeed>
    printMeasurementGeneric(measurement: meas)
}  else if objectValue is Measurement<UnitEnergy> {
    let meas = objectValue as! Measurement<UnitEnergy>
    printMeasurementGeneric(measurement: meas)
} else {
    print("Didn't find cast!")
}

Upvotes: 1

Views: 257

Answers (2)

GetSwifty
GetSwifty

Reputation: 7756

This can be accomplished with a protocol:

protocol HasValue {
    var value: Double { get set }
}
extension Measurement: HasValue { }

func printValue<T: HasValue>(of item: T) {
    print(item.value) // prints "10.1234"
}


var test = Measurement<UnitEnergy>( value: 10.1234, unit: .calories)
printValue(of: test)

Any function similar to printValue should be able to use any measurement generically. You could include the functions in a protocol extension for organization.

Upvotes: 0

Price Ringo
Price Ringo

Reputation: 3440

Change your PrintMeasurementGeneric function to this.

func PrintMeasurementGeneric<Dimension>( measurement: Measurement<Dimension>) {
  print( measurement.value)
}

I used the Dimension type instead of Unit. Either should work but all of the measurement types conform to Dimension (an abstract subclass of Unit).

Hamish (below) is right.

Upvotes: 2

Related Questions