Generic Type Aliases
01 何が問題だったのか
Swift 2 までの typealias は、既存の型に別名を与えるための仕組みでしたが、型パラメータを取れない という制限がありました。つまり、ジェネリクスと組み合わせて「T を後から埋めるための別名」を作ることができません。
たとえば、キーが String で固定された Dictionary に短い名前を付けたい、と考えたとします。素直に書くなら次のようにしたいところですが、これは Swift 2 では書けませんでした。
typealias StringDictionary<T> = Dictionary<String, T> // Swift 2 では不可
同じ事情は、タプルや関数型のように名前を持たない非ノミナル型にも当てはまります。「3要素ともが同じ型 T のタプル」や「T を受け取って Int を返す関数」といった型を、使うたびにフルに書き下すしかありませんでした。
typealias Vec3<T> = (T, T, T) // 書けない
typealias IntFunction<T> = (T) -> Int // 書けない
その結果、ジェネリックな型を使い回す場所では、シグネチャに同じ具体型の構造を何度も書く必要があり、読みにくく保守もしにくいコードになりがちでした。既存の typealias は非ジェネリックな場合にしか役立たず、Swift の他のジェネリクス宣言(struct、class、enum、func)と足並みがそろっていない、というのが素直な実感でした。
02 どのように解決されるのか
typealias に型パラメータを書けるようにします。宣言の名前に続けて <...> で型パラメータを並べると、それを右辺の定義の中で使えるようになります。これにより、ノミナル型の別名も、タプルや関数型のような非ノミナル型の別名も、ジェネリックに定義できます。
typealias StringDictionary<T> = Dictionary<String, T>
typealias DictionaryOfStrings<T: Hashable> = Dictionary<T, String>
typealias IntFunction<T> = (T) -> Int
typealias Vec3<T> = (T, T, T)
typealias BackwardTriple<T1, T2, T3> = (T3, T2, T1)
使う側では、通常のジェネリック型と同じように型引数を与えて利用できます。
let dict: StringDictionary<Int> = ["one": 1, "two": 2]
let triple: Vec3<Double> = (1.0, 2.0, 3.0)
let f: IntFunction<String> = { $0.count }
制約は右辺に合わせて再宣言する
右辺の型がすでに要求している制約は、typealias 側でも明示的に書く必要があります。たとえば Dictionary のキーは Hashable でなければならないので、DictionaryOfStrings<T> で T をキーに使うなら、T: Hashable と宣言し直します。制約を書き忘れると、右辺のほうで要求が満たされないというエラーになります。
typealias DictionaryOfStrings<T> = Dictionary<T, String>
// error: type 'T' does not conform to protocol 'Hashable'
一方で、右辺の要求を超える 追加の 制約を typealias 側で課すことはできません。あくまで「別名」であるという立場を保つための制限で、たとえば次のような書き方はこの提案の範囲外です。
typealias ComparableArray<T where T: Comparable> = Array<T> // 不可
そのほかの性質
ジェネリック typealias も、通常の typealias と同じくアクセス制御(public など)を指定できます。また、typealias は resilient な宣言にはならないという点も変わりません。つまり、別名はコンパイル時に右辺の型へ展開される純粋な別名として振る舞います。