Extensions on bound generic types
01 何が問題だったのか
Swift では、ジェネリック型の型引数を指定する際は山括弧で書くのが一般的で、例えば Array<String> のように書きます。型エイリアスや、SE-0346 で導入された primary associated type の束縛でも同じ書き方が使えます。
typealias StringArray = Array<String>
extension StringArray { ... } // OK
protocol Collection<Element> {
associatedtype Element
}
extension Collection<String> { ... } // OK
ところが、ジェネリック型を直接 extension で拡張しようとすると、この山括弧構文は使えず、次のようなエラーになります。
extension Array<String> { ... }
// error: Constrained extension must be declared on the unspecialized generic type 'Array' with constraints specified by a 'where' clause
同じ意味を表現するには、where 句を使って次のように書く必要がありました。
extension Array where Element == String { ... }
言語のほとんどあらゆる場所で Array<String> と書けるのに、Array<String> を拡張するときだけ書けないというのは一貫性に欠け、「なぜ extension Array<Int> は通らないのか」という疑問がフォーラムでも繰り返し投稿されるなど、学習者にとって分かりにくい制限となっていました。
02 どのように解決されるのか
ジェネリック型を extension で拡張する際にも、型引数を山括弧で書いたり、[String] や Int? といったシュガー表記を使えるようにします。次の 3 つの宣言は、いずれも同じ型に対する extension を表します。
extension Array where Element == String { ... }
extension Array<String> { ... }
extension [String] { ... }
型引数の指定方法
ジェネリック型名のあとに、カンマ区切りの型引数リストを山括弧で続けて書きます。これは where 句で同型制約(same-type requirement)を書くのと等価です。
struct Pair<T, U> {}
extension Pair<Int, String> {}
// 次と等価:
// extension Pair where T == Int, U == String {}
型引数リストを書く場合は、型パラメータをすべて指定しなければなりません。一部だけを束縛したい場合は従来どおり where 句を使います。
extension Pair<Int> {}
// error: Generic type 'Pair' specialized with too few type parameters (got 1, but expected 2)
extension Pair where T == Int {} // OK
型引数には具体型のみを書けます。型プレースホルダ _ は使えません。
extension Pair<Int, _> {} // error: Cannot extend a type that contains placeholders
これは、通常 _ が「この位置の型を推論してほしい」という意味で使われるのに対し、extension での _ だと「その位置を制約しないまま残す」という別の意味になりかねず、同じ記号が文脈によって異なる意味を持ってしまうのを避けるためです。
また、型引数の名前解決は extension の外側のスコープで行われるため、拡張対象のジェネリック型が持つ型パラメータ名を型引数リストの中に書くことはできません。
extension Array<Element> {} // error: Cannot find type 'Element' in scope
シュガー表記
Array<T> や Optional<T> のようにシュガー表記を持つ型については、extension でもそのシュガーが使えます。
extension [String] { ... } // Array<String> を拡張
extension String? { ... } // Optional<String> を拡張
今後の展望
本 Proposal では型引数リストをあくまで同型制約のシュガーとして扱っており、新しい型パラメータを導入する「parameterized extension」は別途の提案に委ねられています。将来的には、extension キーワードの直後に型パラメータリストを書く形で、より柔軟な extension が実現される可能性があります(あくまで構想段階で、本 Proposal での決定事項ではありません)。
// Future direction(本 Proposal の対象外)
extension <Wrapped> [Wrapped?] { ... }
extension [some Equatable] { ... }
extension <T, U> (T, U) { ... }