Andrej Andrej - 16 days ago 11
Swift Question

Generic function for type that implements common interface

I'd like to increase my knowledge of generics and I come to a problem I can't solve. I have two different types (

Int
and
Double
). Both types implement a function
advanced(by:)
. I want to make a generic function to call
advanced(by:)
on a given type.

The benefit would be so that I could replace
intAdvancedByTen(value:)
and
doubleAdvancedByTen(value:)
with a single
genericAdvancedByTen(value:)
.

Here's my playground:

let myDouble: Double = 1
let myInt: Int = 2

func intAdvanceByTen(value: Int) { // Replace this function
value.advanced(by: 10)
}

func doubleAdvanceByTen(value: Int) { // ...and this function
value.advanced(by: 10)
}

protocol CanAdvance {
func advance(_ by: Any)
}
// ...with this generic function
func genericAdvanceByTen(value: CanAdvance) {
value.advance(10)
}

genericAdvanceByTen(value: myInt) // Error: Argument "Int" does not conform to expected type "CanAdvance"


How to make the generic function know that the passed type implements the
advanced(by:)
method?

Answer

Try this:

protocol CanAdvance {
    // This method is intentionally designed to have the same signature as the
    // methods built into Int and Double
    func advanced(by: Self) -> Self

    // We need this primarily for the definition of the constant 10. The built
    // in `advanced` function requires the distance to be of the same type.
    //
    // The conversion syntax in Swift is via init:
    //      let aDouble = Double(anInt)
    // Not the C-like cast:
    //      let aDouble = anInt as! Double    // invalid
    //
    // Hence we must have a way to convert 10 to the appropriate Int or Double.
    // Conveniently, both types can convert from an Int32 so we  put this
    // requirement in the protocol
    init(_ value: Int32)
}

extension Int : CanAdvance { }
extension Double : CanAdvance { }

func genericAdvanceByTen<T: CanAdvance>(value: T) -> T {
    let distance = T(10)
    return value.advanced(by: distance)
}

genericAdvanceByTen(value: 2)       // 12
genericAdvanceByTen(value: 3.14)    // 13.14