2

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?

Andrej
  • 7,266
  • 4
  • 38
  • 57

2 Answers2

1

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
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • Works great, I'm really happy about it. Thank you! :) I was confused as this is the first time I see the use of uppercase `Self` , so here's a link explaining the difference between `self` and `Self` : http://stackoverflow.com/questions/27863810/distinction-in-swift-between-uppercase-self-and-lowercase-self – Andrej Nov 22 '16 at 09:22
1

No need to define your own protocol – advanced(by:) is defined by the standard libary's Strideable protocol:

public protocol Strideable : Comparable {

    /// A type that can represent the distance between two values of `Self`.
    associatedtype Stride : SignedNumber

    // ...

    /// Returns a `Self` `x` such that `self.distance(to: x)` approximates `n`.
    ///
    /// If `Stride` conforms to `Integer`, then `self.distance(to: x) == n`.
    ///
    /// - Complexity: O(1).
    public func advanced(by n: Self.Stride) -> Self
}

Therefore you simply want to constrain your function to take a Strideable input.

Given that the Stride associated type (what advanced(by:) expects as an argument) is constrained to SignedNumber, it must conform to ExpressibleByIntegerLiteral – which allows us to pass an integer literal directly to it.

For example:

func genericAdvancedByTen<T : Strideable>(value: T) -> T {
    // Utilise the fact that T.Stride is ExpressibleByIntegerLiteral.
    return value.advanced(by: 10)
}

print(genericAdvancedByTen(value: 2))       // 12
print(genericAdvancedByTen(value: 3.14))    // 13.14
Hamish
  • 78,605
  • 19
  • 187
  • 280