Swift Digest
SE-0092 | Swift Evolution

Typealiases in protocols and protocol extensions

Proposal
SE-0092
Authors
David Hart, Doug Gregor
Review Manager
Chris Lattner
Status
Implemented (Swift 3.0)

01 何が問題だったのか

SE-0011 により、Swift 2.2 からはプロトコル内で関連型を宣言するためのキーワードが typealias から associatedtype へと変更されました。その結果、プロトコル内における typealias は予約された状態のまま、どの用途にも使えなくなっていました。本来であれば、関連型宣言と区別されたうえで「プロトコル本体の中で型に別名を付ける」ための純粋な型エイリアスとして再利用できるはずのキーワードです。

プロトコル本体で別名を導入できない

Sequence のように関連型を持つプロトコルでは、要素の型を参照するたびに Iterator.Element のような間接的な書き方が必要でした。プロトコル本体の中で Element のような短い別名を導入できないため、where 句や要求のシグネチャが冗長になりがちでした。

func sum<T: Sequence where T.Iterator.Element == Int>(sequence: T) -> Int {
    // ...
}

ここで T.Iterator.Element のかわりに T.Element と書ければ、意図がより直截に伝わります。しかし、プロトコル内で typealias Element = Iterator.Element と書くことは許されていませんでした。

プロトコル拡張側でも型エイリアスを使えない

プロトコル本体だけでなく、プロトコルの extension の中でも typealias による別名が書けなかったため、拡張側で繰り返し現れる長い型参照を手元で短くまとめることもできませんでした。プロトコル適合型に共通して使える補助的な別名を、プロトコル側のコードにまとめて置いておく手段がなかったのです。

SE-0011 で空いたスロットの活用

SE-0011 はキーワードの衝突を避けるための変更であって、プロトコル内に型エイリアスを書くこと自体を禁止する意図ではありませんでした。typealiasassociatedtype が文法上しっかり区別されるようになった以上、プロトコル本体・プロトコル拡張の中でも、従来どおりの意味での typealias が再び使えてよい、というのが素直な整理です。

02 どのように解決されるのか

プロトコル本体と、プロトコルの extension の両方で typealias を書けるようにします。ここで書かれる typealiasassociatedtype とは別物で、従来どおり「既存の型に別名を付ける」ための純粋な型エイリアスです。プロトコル適合側で上書きされることはなく、プロトコル内で閉じた短縮表記として機能します。

関連型を間接的に参照する別名

典型的な用途は、関連型の「さらに先」にある型に短い名前を付けることです。標準ライブラリの Sequence を例に取ると、要素の型 Iterator.ElementElement という別名を与えられます。

protocol Sequence {
  associatedtype Iterator : IteratorProtocol
  typealias Element = Iterator.Element
}

こうしておくと、ジェネリックな関数側では T.Iterator.Element ではなく T.Element で済みます。

func sum<T: Sequence where T.Element == Int>(sequence: T) -> Int {
    return sequence.reduce(0, combine: +)
}

Elementassociatedtype ではなく typealias なので、適合型側でこの名前を別の型に差し替えることはできません。あくまで Iterator.Element の読みやすい表記として固定されます。

プロトコル拡張での別名

プロトコルの extension の中でも typealias が使えるようになります。プロトコル本体に変更を加えなくても、拡張側のコードで使う短縮名を導入できます。

extension Sequence {
    typealias Element = Iterator.Element

    func concat(other: Self) -> [Element] {
        return Array<Element>(self) + Array<Element>(other)
    }
}

関連型との名前衝突

プロトコル内の typealias と、別のプロトコルの associatedtype が同じ名前を持つと、両方に適合する型から見たときに名前が曖昧になることがあります。たとえば Sequencetypealias Element が追加された状態で、別のプロトコルが同名の associatedtype Element を要求するケースです。

protocol MySequence: Sequence {
    associatedtype Element // MySequence.Element is ambiguous
}

これは、typealias を導入したことで新しく生じる問題ではなく、もともと associatedtype 同士でも起こりうる名前衝突と同じ扱いです。衝突が起きた場合は、既存の関連型名の衝突と同じルールで解決します。