SE-0335: Introduce existential any
Introduction
SwiftでExistentialType(存在型)は、型宣言のところでプロトコルの名前を使うだけで、とても簡単に表現できる。
protocol Animal {}
var dog: Animal <- 存在型
長年にわたって、プログラマに間違った使い方をさせ、変更の時に書き直しの手間を発生させるなど、これは紛らわしさの問題を発生させていた。
このプロポーザルでは、存在型を使うときに明示的にany
をつけるようにして、存在型の影響を明示的にする。
Motivation
Swiftで存在型では、制約の大きさとパフォーマンスの問題がある。
いくつかの制限は、言語の機能が欠けていることからきているが、
多くは、型消去乗せマンティクスの基本的なことである(?)
例えば、associated Typeが必要なprotocolを考える。
存在型は、それ自身ではプロトコルを満たすことはできず、満たすように実装することが必要である。
なぜなら、そのプロトコルを満たす任意の方に対して機能する 明確な具体的なassociatedTypeがないからである。
protocol P {
associatedtype A
func test(a: A)
}
func generic<ConcreteP: P>(p: ConcreteP, value: ConcreteP.A) {
p.test(a: value)
}
func useExistential(p: P) {
generic(p: p, value: ???) // what type of value would P.A be??
}
また、存在型は具体的な方を使う場合に比べて、expensive。(メモリを多く使用する)?
なぜなら、存在型はそのプロトコルを満たす任意の型を入れることができるからである。そして、その型は動的に変えることができる。
そのため、どんなにその型のメモリ使用量が少なくても、メモリを動的に確保できるようにする必要がある。
さらに、ヒープ領域やARCのために、存在型を使うコードは、ポインタの間接参照と メソッドの動的ディスパッチをする必要がある。それらは最適化できない。 (<- https://youtu.be/HygLwTRO-Zw?si=32BjI8-ajejzi7Fj 多分この辺の話)
protocol Animal {}
struct Dog: Animal {}
struct Cat: Animal {}
var anyAnimal: anyAnimal = Dog()
anyAnimal = Cat ()
これらの 影響が大きく、よく発生する望まない 非明示的な動作に関わらず、存在型は簡単に使用できる。
書くときは、それらの代償を意識することはなく、generics制約のような書き方は、多くのプログラマーにとって存在型とGenericsが紛らわしいものとなっている。
現実では、そのような動的な型はgenericsに比べて、あまり需要がないが、Swiftの文法的に、存在型が簡単に使えてしまうので、多くの人が誤用してしまっている。
存在型を使う代償は明示的であるべきで、使うときは 使うことを意識して使えるようにするべきである。
Proposed solution
any
keywordを使って、文法的に、明示的に、存在型を使うようにする。
Swift5のコンパイラーで利用可能。
// Swift 5 mode
protocol P {}
protocol Q {}
struct S: P, Q {}
let p1: P = S() // 'P' in this context is an existential type
let p2: any P = S() // 'any P' is an explicit existential type
let pq1: P & Q = S() // 'P & Q' in this context is an existential type
let pq2: any P & Q = S() // 'any P & Q' is an explicit existential type
In Swift 6, existential types are required be explicitly spelled with any:
// Swift 6 mode
protocol P {}
protocol Q {}
struct S: P, Q {}
let p1: P = S() // error
let p2: any P = S() // okay
let pq1: P & Q = S() // error
let pq2: any P & Q = S() // okay
Swift6の挙動は、ExistentialAny
flagで有効にすることができる。
Detailed design
明示的に存在型を宣言する文法
type -> existential-type
existential-type -> 'any' type
存在型のセマンティクス
any
型は存在型と同じ意味である。
any
は、プロトコルと、その複合体、また、メタタイプのみに適応できます。
nominal型、structured型(<- nominalとの対応)、型パラメータ、プロトコルメタタイプには使用できない。
struct S {}
let s: any S = S() // error: 'any' has no effect on concrete type 'S
func generic<T>(t: T) {
let x: any T = t // error: 'any' has no effect on type parameter 'T'
}
let f: any ((Int) -> Void) = generic // error: 'any' has no effect on concrete type '(Int) -> Void'
Any and AnyObject
any
は、Any型やAnyObject型には不要である。
struct S {}
class C {}
let value: any Any = S() <- anyなくても動く
let values: [any Any] = [] <- anyなくても動く
let object: any AnyObject = C() <- anyなくても動く
protocol P {}
extension C: P {}
let pObject: any AnyObject & P = C() // anyがないとエラーになる。
AnyやAnyObjectに
any
をつけるのは冗長である。それらは特別な型で、名前から明示的にわかるので、明示的にanyをつけなくてもいいという理解。
Metatypes
存在的 MetaType (P.Typeは any P.Typeになる)
プロトコルMeta Type(P.Protocolは、 (any P).Typeになる)
プロトコルMetaTypeの値 P.selfは、(any P).selfになる。
protocol P {}
struct S: P {}
let existentialMetatype: any P.Type = S.self
protocol Q {}
extension S: Q {}
let compositionMetatype: any (P & Q).Type = S.self
let protocolMetatype: (any P).Type = (any P).self
(Note): 存在型の Meta Typeは any P.Typeのように表現する。それは、MetaTypeの一般化だからである。
プロトコルのMetaTypeは(any P)自身のMetaTypeなので、(any P).Typeと書く。
このモデルでは、any
キーワードは、概念的には existential quantifier ∃
と同じように振る舞う。
正式には、any P.Type
は ∃ T:P . T.Type
を意味する。(Pを満たす具体的な型Tが存在し、T.Type)
any P.Type
と(any P).Type`の文法的な違いはとても些細なものである。
しかし、(any P).Typeは滅多に実用例がない。
それは 型パラメーター T が存在型に置き換えられる一般的なコンテキストを考えると、T.Type がシングルトン プロトコル メタタイプである理由を説明するのに役立ちます?
Metatypes for Any and AnyObject
基本的な型のように、Any.TypeとAnyObject.Typeは 存在型MetaTypeである。
AnyとAnyObjectのMetaTypeは(any Any).Tpye、(any AnyObject).Typeのようにかける。
Type aliases and associated types
プロトコルPに対するtypealiasは Generics制約としても、存在型としても使える。
any
は存在型なので、anyPのtypealiasは 存在型として使われる必要がある。
generics制約を満たすために使われることはできない。anyを使う側から書く必要もない。
protocol P {}
typealias AnotherP = P
typealias AnyP = any P
struct S: P {}
let p2: any AnotherP = S()
let p1: AnyP = S()
func generic<T: AnotherP>(value: T) { ... }
func generic<T: AnyP>(value: T) { ... } // error
一度、any
がSwift6以下で必要になったら、プロトコル名のTypealiasは associatedTypeの制約として機能しなくなる?
any付きのtypealiasで書く必要がある。
// Swift 6 code
protocol P {}
protocol Requirements {
associatedtype A
}
struct S1: Requirements {
typealias A = P // error: associated type requirement cannot be satisfied with a protocol
}
struct S2: Requirements {
typealias A = any P // okay
}
Nominal Typeとは https://speakerdeck.com/takasek/what-is-a-nominal-type
互換性
any
を必須にするには、ソースコードの変更が必要になる。
変更を簡単にするために、swift5.6でany
をつけれるようにする(必須ではない)。Swift6から必須になる。
Swift5では、既存の非明示的な存在型の宣言は、有効なままである。
単純な変更なので、機械的に自動でできそう。
SE-309(Unlock existentials for all protocols)では、多くのコードで、存在型を使用できるようになった。
Swift6で無効になる存在型の使用方法を少なくするために、私は、Self およびassociatedTypeの要件を持つプロトコルに対して any を直ちに要求することを提案します。
それによって、Swift5のモードでは非一貫性が生じるが、それはすでに存在してるものなので大した問題ではないだろう(なぜなら、ある種のプルとコルは存在型として、全くとして、使用できないから)
そして、その構文の違いは、二つの利点がある
- Swift6で無効になるコードを欠かせないようにすることで、将来 対応する必要がなくなり、時間の節約になる。
- Swift6の準備として、プログラマーがanyに慣れることができる
Transitioning to any in Swift 6
anyが導入されると、compilerはwarningを表示しないようになり、コンパイルエラーになる。
Alternatives considered
Rename Any and AnyObject
Any. AnyObjectを"any Value" "any Object"にする。
これはプロポーザルとの一貫性があるが、より多くの変更が必要になる。
AnyとAnyObjectは他の存在型より害悪でないため(Anyと明示されているから?)、変更のコストに対して、得られるメリットが小さそうなので、変更しないことにした。
Use Any<P> instead of any P
any
をキーワードじゃなくて、Any<P>みたいな書き方にする方法。
そうすると、some
とany
の対称性がなくなる。
anyとsomeが似た文法になっていることで、置き換えやすくなる。
事実として、anyはsomeで置き換えられる場合が多い。 someの方がメモリに優しいので?置き換えられるなら置き換えた方が良さそう。
最後に、Genericの文法と間違えやすくなる。
Future Directions
Extending existential types
protocolのexntentionにany
をつけれるようにする。
extension any Equatable: Equatable { ... }
Re-purposing the plain protocol name
In other places in the language, a plain protocol name is already sugar for a type parameter conforming to the protocol. Consider a normal protocol extension:
extension Collection { ... }
このextentionは、Collection プロトコルと継承する、すべてのタイプに適応される。
以下のものと同じ働き。
extension <Self> Self where Self: Collection { ... }
any
の構文を変えることによって、上記の糖衣構文を拡張できる。
もし、存在型が明示的に宣言する必要があるなら、"protocol名をただ書くこと"は常に、プロトコルを継承することを要件とする型パラメータとしての糖衣構文を意味するようになる。
extension Array {
mutating func append<S: Sequence>(contentsOf newElements: S) where S.Element == Element
}
associatedTypeを"<>"で宣言する構文と組み合わせることで、上の宣言は、下のようにシンプルに描けるようになる。
extension Array {
mutating func append(contentsOf newElements: Sequence<Element>)
}
This sugar eliminates a lot of noise in cases where a type parameter is only referred to once in a generic signature, and it enforces a natural model of abstraction, where programmers only need to name an entity when they need to refer to it multiple times.