Swift Digest
SE-0011 | Swift Evolution

Replace typealias keyword with associatedtype for associated type declarations

Proposal
SE-0011
Authors
Loïc Lecrenier
Review Manager
Doug Gregor
Status
Implemented (Swift 2.2)

01 何が問題だったのか

Swift 2.x 当時、プロトコル内で関連型(associated type)を宣言するためのキーワードは typealias でした。typealias は、既存の型に別名を付ける「通常の型エイリアス」としても使われるキーワードであり、プロトコル内ではそれが「関連型の宣言」として別の意味を持つ、という二重の使い方になっていたのです。この提案は、関連型の宣言に専用のキーワードを割り当てて、この二つを文法上も区別しようとするものでした。

同じ typealias が文脈で意味を変える

同じ typealias というキーワードが、プロトコルの内側と外側とで異なる意味を持つことは、初学者にとって大きな混乱のもとでした。プロトコルの外では「既存の型に別名を付ける」のに対し、プロトコルの中では「プロトコル適合側が埋めるべきプレースホルダーとしての型を宣言する」という、まったく別の概念を表していたからです。見た目が同じ構文なので、どちらの意味で書いているのかがコードから読み取りにくい状況でした。

初期値付きで関連型を宣言しているのに気付けない

特に厄介なのが、関連型に初期値(デフォルト)を与えた場合の誤読です。次のようなコードは、書き手が「ElementContainer.Generator.Element の型エイリアス」のつもりでも、実際には「Element というデフォルト値付きの関連型」を宣言したことになってしまいます。

protocol Prot {
    typealias Container : SequenceType
    typealias Element = Container.Generator.Element
}

この Element は、プロトコル適合側が別の型で上書きできる関連型です。書き手はエイリアスを定義したつもりなのに、意味はまったく異なるものになっています。

プロトコル内では通常の型エイリアスを書けないことも見えにくい

プロトコル本体の内側に通常の型エイリアスを書くことはできず、そうしたい場合はエクステンション側に書く必要があります。

protocol Prot {
    typealias Container : SequenceType
}
extension Prot {
    typealias Element = Container.Generator.Element
}

しかし typealias というキーワードが両方の用途で使い回されているため、「プロトコル本体には書けず、エクステンションには書ける」という線引きがコードから伝わりにくく、前述の誤読とあいまって、Swiftを学び始めた利用者がつまずきやすいポイントになっていました。

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

プロトコル内で関連型を宣言するためのキーワードを typealias から associatedtype に変更します。これにより、typealias は「既存の型に別名を付ける」用途のみに使われるキーワードとなり、関連型の宣言は associatedtype という専用キーワードで表現されるようになります。

associatedtype による宣言

関連型は、プロトコル本体の中で associatedtype を使って宣言します。制約やデフォルト値の指定方法は従来の typealias と同じです。

protocol Prot {
    associatedtype Container : SequenceType
    associatedtype Element = Container.Generator.Element
}

この書き分けにより、Element が「デフォルト値付きの関連型」であることがキーワードから一目で分かるようになり、型エイリアスとの取り違えが起こりません。

プロトコル内で型エイリアスを書こうとするとエラー

プロトコル本体の中に通常の型エイリアスを書こうとすると、コンパイラが明確にエラーを返し、エクステンションに書くよう促します。

protocol Prot {
    associatedtype Container : SequenceType
    typealias Element = Container.Generator.Element // error: cannot declare type alias inside protocol, use protocol extension instead
}

通常の型エイリアスとしてプロトコルに紐付けたい場合は、従来どおりエクステンション側に書きます。

protocol Prot {
    associatedtype Container : SequenceType
}
extension Prot {
    typealias Element = Container.Generator.Element
}

移行

単純なキーワードの置き換えなので、既存コードの typealias による関連型宣言は機械的に associatedtype に書き換えるだけで済みます。Swift 2.2 で associatedtype が導入され、プロトコル内での typealias はdeprecatedとなり、Swift 3 で完全に associatedtype のみが使えるようになりました。