開発メモ

開発関係のメモをいろいろと。たぶん。

SwiftのGenericsでハマったメモ

SwiftのGenericsで少し凝ったこと(?)をやろうとしてハマったのでメモ。

動作を確認した環境

環境 情報
Xcode 6.3 (6D570)
iOS 8.3
Swift 1.2
Date 2015/5/15

1. はじめに

普通に、線形補間を行う関数を書いてみた。とりあえず、型はIntで。

/// 単純な線形補間(Int版)
func interpolation(y0: Int, y1: Int, x0: Int, x1: Int, x: Int) -> Int {
    if x0 == x1 {
        return y0
    } else {
        return (y0 * (x1 - x) + y1 * (x - x0)) / (x1 - x0)
    }
}

Playgroundで実験するとこんな感じ。

f:id:see_ku:20150515203455p:plain

やっぱり、Doubleが欲しくなったのでDouble版を別に作成。

/// 単純な線形補間(Double版)
func interpolation(y0: Double, y1: Double, x0: Double, x1: Double, x: Double) -> Double {
    if x0 == x1 {
        return y0
    } else {
        return (y0 * (x1 - x) + y1 * (x - x0)) / (x1 - x0)
    }
}

次に、CGFloat版が欲しくなって・・・ あまりにも無駄が多いので、Generics化を検討することに。

2. 単純にGenerics

とりあえず、単純にGenerics化してみた。

/// 単純な線形補間(Generics版 - NG)
func interpolation<T>(y0: T, y1: T, x0: T, x1: T, x: T) -> T {
    if x0 == x1 {
        return y0
    } else {
        return (y0 * (x1 - x) + y1 * (x - x0)) / (x1 - x0)
    }
}

予想通り、エラーが出てコンパイルできない。

f:id:see_ku:20150515203511p:plain

これがC++のtemplateならダックタイピングのノリで適当に取り扱ってくれるんだろうけど、厳密にチェックしてくれるのがSwiftのいいところ・・・ かな?

3. プロトコルを探す

『==』が使えないエラーが出てるので、これが使えるように、制約でEquatableプロトコルを設定。

/// 単純な線形補間(Generics版 - NG)
func interpolation<T: Equatable>(y0: T, y1: T, x0: T, x1: T, x: T) -> T {
    if x0 == x1 {
        return y0
    } else {
        return (y0 * (x1 - x) + y1 * (x - x0)) / (x1 - x0)
    }
}

これで『==』は使えるようになったけど、今度は別のエラーが発生。

f:id:see_ku:20150515203523p:plain

今度は『+』が使えないのが問題に。

interpolationの中では四則演算をすべて使ってるのでこれに適応したプロトコルがあれば良いんだけど、今のSwiftにはそんなものはない。

Swiftの整数型はIntegerArithmeticTypeに適応しているので、これを制約に使えば上手くいきそうな気がするけど、そうすると、DoubleやFloatが使えなくなってしまう。

では、どうするべきか?

4. プロトコルを作成

無ければ自分で作ってしまえばいい。というわけで、自分でプロトコルを作って適応させる。

/// 線形補間で必要になるプロトコル
protocol InterpolationArithmeticType: Equatable {
    func +(lhs: Self, rhs: Self) -> Self
    func -(lhs: Self, rhs: Self) -> Self
    func *(lhs: Self, rhs: Self) -> Self
    func /(lhs: Self, rhs: Self) -> Self
}

/// 使いたい型にプロトコルを適応させておく
extension Int: InterpolationArithmeticType {
}

extension Double: InterpolationArithmeticType {
}

extension CGFloat: InterpolationArithmeticType {
}

/// 単純な線形補間(Generics版 - OK)
func interpolation<T: InterpolationArithmeticType>(y0: T, y1: T, x0: T, x1: T, x: T) -> T {
    if x0 == x1 {
        return y0
    } else {
        return (y0 * (x1 - x) + y1 * (x - x0)) / (x1 - x0)
    }
}

たかが線形補間1つにずいぶん大げさな気がしないでもないけど、今のSwiftではこれが正解っぽい。たぶん。

Genericsな関数の中で、さらに別の機能が必要になることもあるけど、そういう時は、自作のprotocolでその機能を使えるようにすれば良い、と。ごくごく単純な整数との比較まで自力でフォローしないといけないのは面倒だけど、こればっかりはあきらめて対処するしか無さそう。

どちらかと言うと現状での最大の問題点は・・・ Genericsを重ねて使うと、Playgroundが死ぬほど重くなることだったり?