主要associated typesのための軽量なsame-type要件
Lightweight same-type requirements for primary associated types
このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら↗。
01 何が問題だったのか
ジェネリクスの利用において、プロトコルに適合する型パラメータの associated type を単一の同値(same-type)要件で固定したい場面は頻繁にあります。しかし、この種の制約は従来 where 句でしか表現できず、読み書きの両面で負担が大きいという問題がありました。
たとえば、String のシーケンスを二つ連結する関数を任意のシーケンス型に一般化しようとすると、次のように書く必要があります。
func concatenate<S: Sequence>(_ lhs: S, _ rhs: S) -> S where S.Element == String {
...
}
具象型であれば Array<String> と一言で済むのに対し、プロトコル越しに同じことを言うだけのために型パラメータ S を導入し、where 句で S.Element == String を書く必要があります。単一の same-type 要件しかない単純なケースであっても、この構文的な重さは避けられません。
また、opaque result type の場合はさらに深刻で、そもそも where 句を書く場所がありません。次のように結果型を隠蔽しようとすると、
func readSyntaxHighlightedLines(_ file: String) -> some Sequence {
...
}
要素型が [Token] であるという情報が失われ、呼び出し側は結果を Sequence としてしか扱えなくなってしまいます。Array<[Token]> のように要素型を含めて公開する手段が、プロトコル側には用意されていなかったのです。
より根本的には、Swift の学習者にとって Array<Int> のような具象ジェネリック型の構文は自然に受け入れられる一方、Collection の要素型を同じように書き表す手段が無いことは、具象型とジェネリクスの間の不必要な非対称性になっていました。
02 どのように解決されるのか
プロトコル側で primary associated type を宣言できるようにし、利用側では Collection<Int> のように具象ジェネリック型と同じ構文で主要な associated type を固定できるようにします。
プロトコル側の宣言
プロトコル名の直後に山カッコで primary associated type のリストを書きます。そこに並べる名前は、プロトコル本体(あるいは継承元)で associatedtype として宣言されている associated type でなければなりません。
protocol Sequence<Element> {
associatedtype Element
associatedtype Iterator: IteratorProtocol
where Element == Iterator.Element
...
}
protocol DictionaryProtocol<Key, Value> {
associatedtype Key: Hashable
associatedtype Value
...
}
primary associated type の役割は「通常は利用側が指定する associated type」を明示することです。Array<Element> や Set<Element> が Sequence に適合するときに具象型のジェネリックパラメータで埋められるのがまさに Element であり、このような associated type を primary として選ぶのが想定される使い方です。
利用側の構文
primary associated type を持つプロトコルは、プロトコル適合要件を書けるあらゆる位置で、P<Arg1, Arg2, ...> のように型引数を伴って書けます。引数の個数は primary associated type の個数と一致している必要があります。山カッコを省略して従来どおり制約なしで書くこともでき、したがって primary associated type の追加は既存コードに対して source-compatible な変更です。
where 句を使った書き方との対応は次のとおりです。
// extension の対象型
extension Collection<String> { ... }
// 従来: extension Collection where Element == String { ... }
// 別プロトコルの継承節
protocol TextBuffer: Collection<String> { ... }
// ジェネリックパラメータの継承節
func sortLines<S: Collection<String>>(_ lines: S) -> S
// associated type の継承節
protocol Document {
associatedtype Lines: Collection<String>
}
// where 句内の適合要件の右辺
func merge<S: Sequence>(_ sequences: S)
where S.Element: Sequence<String>
// opaque parameter(SE-0341)
func sortLines(_ lines: some Collection<String>)
いずれも、T: P<Arg1, Arg2, ...> は T: P と T.PrimaryType1 == Arg1、T.PrimaryType2 == Arg2、…という same-type 要件の組み合わせに展開されます。
ネストした opaque parameter も書けます。
func sort(elements: inout some Collection<some Equatable>) {}
// 従来の書き方では次と等価:
// func sort<C: Collection, E: Equatable>(elements: inout C) where C.Element == E
opaque result type での新しい表現力
opaque result type(some P)でも同じ構文が使えます。これは単なる糖衣ではなく、従来書けなかったことを表現可能にする点が重要です。opaque result type には where 句を付けられないため、戻り値の primary associated type を固定する手段が無かったからです。
func readSyntaxHighlightedLines(_ file: String) -> some Sequence<[Token]> {
...
}
外側のスコープのジェネリックパラメータを引数として渡すこともできますし、some のネストも可能です。
func transformElements<S: Sequence<E>, E>(_ lines: S) -> some Sequence<E>
func transform(_: some Sequence<some Equatable>) -> some Sequence<some Equatable>
後者で、引数側と戻り値側の some Sequence<some Equatable> は無関係な別の型である点に注意してください。引数側は呼び出し側が選ぶ型、戻り値側は実装が返す(別の)均質なシーケンスです。
その他の利用位置
次の場所でも同じ構文が使えます。
- 具象型の継承節。
struct Lines: Collection<String> { ... }はElementの witness をtypealias Element = Stringで指定するのと同じです。 typealiasの右辺。typealias SequenceOfInt = Sequence<Int>のように定義して、プロトコル型が書ける位置で利用できます。- プロトコル合成の一部。
some Sequence<Int> & Equatableのように書けます。
existential は対象外
any Collection<String> のような制約付き existential 型は本Proposalのスコープ外です。型変換や動的キャスト、メタデータまわりの扱いが別途必要になるため、別のProposalで扱われます。
03 今後の見通し
本Proposalで構文と意味論の土台が整う一方、その応用は今後の検討課題として残されています。いずれも将来の方向性として示されているものであり、実現を約束するものではありません。
標準ライブラリへの導入
Sequence や Collection をはじめとする標準ライブラリのプロトコルに、実際に primary associated type を導入する作業は本Proposalの範囲外です。Element のように「primary」とすべき associated type の候補が明らかなものもあれば、議論を要するものもあり、それぞれ別途検討されることになります。
制約付き existential
any Collection<String> のように、existential 型に対しても primary associated type を固定する構文を許す拡張が考えられています。ただし、型変換や動的キャストの挙動、メタデータや実行時サポートを別途整備する必要があるため、本Proposalでは対象外とされ、別のProposalで扱われることになっています。