2

The best way to describe what I want is by this example:

protocol Lerpable {
    // here should be 'lerp<T: Lerpable>(_ x: CGFloat, _ a: T, _ b: T) -> T' function
}
extension CGFloat: Lerpable {}
extension CGPoint: Lerpable {}
extension CGRect: Lerpable {}

func lerp(_ x: CGFloat, _ a: CGFloat, _ b: CGFloat) -> CGFloat {
    return a * (1.0 - x) + b * x
}

func lerp(_ x: CGFloat, _ a: CGPoint, _ b: CGPoint) -> CGPoint {
    return CGPoint(x: lerp(x, a.x, b.x), y: lerp(x, a.y, b.y))
}

func lerp(_ x: CGFloat, _ a: CGRect, _ b: CGRect) -> CGRect {
    return CGRect(x: lerp(x, a.x, b.x), y: lerp(x, a.y, b.y), width: lerp(x, a.width, b.width), height: lerp(x, a.height, b.height))
}

func lerpKeyframes<T: Lerpable>(_ x: CGFloat, array: [T]) -> T? {
    if x <= 0 {
        return array.first
    }
    else if x >= (array.count - 1) {
        return array.last
    }
    else {
        let leftBound = Int(x)
        let fraction = fmod(x, 1.0)
        return lerp(fraction, array[leftBound], array[leftBound + 1]) // ERROR here, can't recognize implemented 'lerp' method
    }
}

How to write this code, so I could use lerpKeyframes(...) for CGFloat, CGPoint and CGRect?

brigadir
  • 6,874
  • 6
  • 46
  • 81
  • 1
    This fails because there's no guarantee that `T` is a `CGFloat`, `CGPoint` or `CGRect`. What if it's some other Lerpable type, for which there is no `leap` function specialization? – Alexander Nov 14 '16 at 23:24

1 Answers1

4

This is where protocols with associated Self requirements come in. Here's a rough example:

import Foundation

protocol LinearlyInterpolatable {
    func interpolate(with: Self, by: CGFloat) -> Self;
}

extension CGFloat: LinearlyInterpolatable {
    func interpolate(with other: CGFloat, by k: CGFloat) -> CGFloat {
        return self * (1.0 - k) + other * k
    }
}

extension CGPoint: LinearlyInterpolatable {
    func interpolate(with other: CGPoint, by k: CGFloat) -> CGPoint {
        return CGPoint(
            x: self.x.interpolate(with: other.x, by: k),
            y: self.y.interpolate(with: other.y, by: k)
        )
    }
}

extension CGRect: LinearlyInterpolatable {
    internal func interpolate(with other: CGRect, by k: CGFloat) -> CGRect {
        return CGRect(
            x: self.x.interpolate(with: other.x, by: k),
            y: self.y.interpolate(with: other.y, by: k),
            width: self.width.interpolate(with: other.width, by: k),
            height: self.height.interpolate(with: other.height, by: k)
        )
    }
}

func lerpKeyframes<T: LinearlyInterpolatable>(_ k: CGFloat, array: [T]) -> T? {
    let leftBound = Int(k)
    guard 0 <= leftBound else { return array.first }
    guard leftBound < array.count else { return array.last }

    let fraction = fmod(k, 1.0)

    return array[leftBound].interpolate(with: array[leftBound + 1], by: fraction)
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • I guess you can answer [this](http://codereview.stackexchange.com/questions/146736/adding-swipe-support-to-multiple-classes) too – LC 웃 Nov 15 '16 at 02:39
  • And what makes you think that? :p – Alexander Nov 15 '16 at 02:40
  • Both are related..I was looking for similar kind problem. – LC 웃 Nov 15 '16 at 02:42
  • @AlexanderMomchliov, thanks! It works. Strange, why `func lerp(_ x: CGFloat, _ a: Self, _ b: Self) -> Self` doesn't work? Compiles with error `Use of unresolved identifier 'lerp'` when trying to call this function. – brigadir Nov 16 '16 at 20:55