Swiftのassociatedtypeとtype erasure

(2020-06-24)

associatedtypeはprotocolのジェネリクスのようなもので、複数の型に対応した定義を書くことができる。

protocol P1 {
    associatedtype T
    func some(x: T)
    func some2(x: T)
}

class C1: P1 {
    func some(x: Int) {
        print(x)
    }
    /*
    // ambiguous inference of associated type 'T': 'String' vs. 'Int'
    func some2(x: String) {
        print(x)
    }
    */
    func some2(x: Int) {
        print(x)
    }
}

class C2: P1 {
    func some(x: Int) {
        print(x)
    }
    // OK
    func some2<T>(x: T) {
        print(x)
    }
}

通常、protocolはexistential typeとして変数の型に指定できるが、 associatedtypeが含まれるとその型が不明なので指定できない。

// protocol 'P1' can only be used as a generic constraint because it has Self or associated type requirements
func f(x: P1) {
}

ジェネリクスの型パラメータの型には指定できる。

func f<T:P1>(x: T, value: T.T) {
    x.some(x: value)
}

ということで次のようなクラスでラップすればインタフェースを変えずにこれを共通の型として指定できる。 protocolやこのようなラッパークラスによって元の型の情報を失わせる処理をtype erasureと呼ぶようだ。

class AnyP1<T, U: P1>: P1 where U.T == T {
    var base: U
    init(_ base: U)
    {
        self.base = base
    }
    
    func some(x: T) {
        base.some(x: x)
    }
    func some2(x: T) {
        base.some2(x: x)
    }
}

var x: AnyP1 = AnyP1(C1())

参考

Swift の Type Erasure の実装パターンの紹介 - Qiita

Keep Calm and Type Erase On