Swift Digest
SE-0095 | Swift Evolution

Replace protocol<P1,P2> syntax with P1 & P2 syntax

Proposal
SE-0095
Authors
Adrian Zubarev, Austin Zheng
Review Manager
Chris Lattner
Status
Implemented (Swift 3.0)

01 何が問題だったのか

Swift には、「複数のプロトコルのすべてに適合する何らかの型」を表すための existential 型の構文として、protocol<...> という書き方がありました。プロトコルをカンマ区切りで並べ、それらすべてに適合する型を表す合成型を作るためのものです。

protocol A { }
protocol B { }
protocol C { }

struct Foo: A, B, C { }

// A にも B にも C にも適合する型
let value: protocol<A, B, C> = Foo()

// 引数の型としての使用
func firstFunc(x: protocol<A, B>) { ... }

// ジェネリクス制約としての使用
func secondFunc<T: protocol<A, B>>(x: T) { ... }

この protocol<...> 構文にはいくつか問題がありました。

読みにくく、意図が伝わりにくい

protocol<A, B> は「プロトコル合成」を表しますが、見た目は「protocol という総称的な型に A, B を型引数として与えた」ようにも読めます。プロトコルの合成(conjunction)を表す構文として、<> とカンマの組み合わせは意図を直接的に表現できていませんでした。

今後の拡張の妨げになる

Swift のジェネリクスの将来像を示した Completing Generics では、existential 型を where 節付きで拡張するなど、より一般化された existential(generalized existentials)への道筋が描かれていました。その基盤として existential 型の構文を整えておく必要があったものの、protocol<...> のままでは新しい機能を載せる土台として扱いづらく、拡張したときに既存機能と意味が重なって混乱しやすい形でした。

破壊的変更を入れられるタイミングが限られていた

この種の構文変更は後方非互換を伴うため、Swift 3 のように大きな破壊的変更を受け入れるタイミングでないと入れにくいという事情もありました。existential 構文の整理を Swift 3 で済ませておけば、将来 generalized existentials を入れる際に、破壊的変更なしで機能を拡張できるようになります。

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

protocol<...> 構文を廃止し、代わりにプロトコル名同士を中置演算子 & で連結する新しい型構文を導入します。複数のプロトコルに適合する型を表す existential 型は、次のように書きます。

protocol A { }
protocol B { }
protocol C { }

struct Foo: A, B, C { }

// A にも B にも C にも適合する型
let value: A & B & C = Foo()

カンマではなく & を使うことで、「A かつ B かつ C」という合成の意図が見た目にそのまま現れるようになります。

関数シグネチャやジェネリクス制約でも同様に使える

引数の型としても、ジェネリクス制約としても、& でプロトコルを連結した型をそのまま書けます。

protocol A { }
protocol B { }

// existential: A にも B にも適合する何らかの型を受け取る
func firstFunc(x: A & B) { ... }

// ジェネリクス: A にも B にも適合する具体型 T を受け取る
func secondFunc<T: A & B>(x: T) { ... }

Any はキーワード化される

従来 protocol<>(プロトコルを何も列挙しない protocol<>)は「すべての型に適合する型」、つまり任意の型を表していて、その別名として Any という typealias が用意されていました。本提案では protocol<> を廃止するのに合わせて、Any を単なる typealias ではなくキーワードに格上げします。意味はこれまでと同じで、「任意の型」を表します。

let anything: Any = 42       // 任意の型を受け取れる

既存コードへの影響

protocol<...> を使っているコードは、新しい構文に書き換える必要があります。

// Swift 2 まで
func f(x: protocol<A, B>) { ... }
let x: protocol<> = 42

// Swift 3 以降
func f(x: A & B) { ... }
let x: Any = 42

Any を単独で使っていた既存コードは、キーワード化後も意味が変わらないため影響を受けません。影響があるのは protocol<...> を明示的に使っていたコードに限られます。

今後の展望

& による existential 構文は、将来 generalized existentials(where 節を伴う existential など)を導入するときの土台として機能することが意図されています。たとえば Collection where .Element == Int のような、関連型に制約を付けた existential を書けるようにする、といった拡張を、本提案の構文の延長線上で検討できるようになります。これはあくまで今後の方向性を示すもので、実現が約束されているものではありません。