ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Swiftでプロトコルインターフェイスを設計する
Swift 5.7で、プロトコルを使用した高度な抽象化を設計する方法をご覧ください。既存タイプの使用方法をはじめ、オパークリザルトタイプを使用して実装をインターフェイスから分離する方法や、特定タイプ間の関係性を同定したり保証したりできる同種要件を紹介します。 このセッションを最大限に活用するには、WWDC22の「Swiftのジェネリクスを活用する」を最初にご覧ください。
リソース
関連ビデオ
WWDC23
WWDC22
-
ダウンロード
(音楽)
どうも Swift Compiler teamのSlavaです 「Swiftでプロトコル インターフェイスを設計する」へようこそ ここでは「Swiftのジェネリクスを 活用する」に引き続き プロトコルを使って具象型及びモデル型関係を 抽象化する上級テクニックをご紹介します ここでは既存の言語機能と Swift 5.7で登場した 新機能についてお話しします トピックは3つです まず 関連型のプロトコルが 存在型とどうインタラクトするか 結果型消去について説明しながらお見せします 次にopaque result typesを使い 実装からインターフェイスを分け カプセル化を向上させる方法をお見せします 最後に プロトコルのsame-type requirementsによる 具象型セットの関係構築をお見せします
まず関連型プロトコルが どう存在型とインタラクト するか見てみましょう ここに2つのプロトコルと4つの具象型の データモデルがあります 2種類の動物鶏(Chicken)と牛(Cow)があり 2種類の食べ物 卵(Egg)と ミルク(Milk)があります 鶏が卵をproduceし牛がミルクをproduceします 食べ物の生産を抽出するため produce()メソッドを Animalプロトコルに足します 「Swiftのジェネリクスを活用する」で 牛と鶏のproduce()で違った戻り値の型を 抽出するのに最適な方法は 関連型を使うことだと学びました 関連型を使うことで 動物の具象型で produce()をコールするとAnimalの具象型に頼る 特定の食べ物が返ると宣言することになります この関係をダイヤグラムで見てみましょう Animalプロトコルに一致のSelf型プロトコルが 実際の具象型の代わりになります Self型には関連型Commodityタイプがあり Foodに準拠します では具象型のChickenと CowタイプとAnimalプロトコルの 関連型ダイヤグラムの関係を見てみましょう
ChickenタイプはEggのCommodityタイプで CowタイプはMilkでAnimal プロトコルに準拠します 農場(Farm)にたくさん動物がいるとします FarmのAnimalプロパティは any Animalの異種起源配列で 「Swiftのジェネリクスを活用する」では any Animalタイプが どの具象型Animalも動的に格納できる ボックス表現があるとお話ししました 違った具象型に同じ表現を 使用することを型消去と言います
produceCommodities()は animal配列をマッピングし それぞれにproduce()を呼び出します シンプルに見えますが型消去は 基底型Animalへの静的関係を削除するので なぜこうなるのか理解する必要があります
map() closureのAnimal パラメータはany Animalで produce()の戻り値の型は関連型です 存在型に関連型を戻すメソッドを返す時 コンパイラは型消去で戻り値の型を決定します 型消去はこれらの関連型を 同様の制約の存在型に置き換えます any Animalとany Foodとで 置き換えることにより 具象型Animalと 関連型Commodityの関係を削除したのです any Foodは 関連型Commodityの上限と呼ばれます produce() がany Animalに使われたので 戻り値の型が型消去され any Foodの値が与えられ これは予想通りです
では関連型消去を見てみます Swift 5.7の新機能です 矢印の右にあるプロトコルの 戻り値の型に現れた 関連型は メソッドを呼ぶことで この型の値を出すための生産位置にあります any Animalでこのメソッドを呼ぶと コンパイル時の具象型はわかりませんが 何らかの上限のサブタイプで あることはわかっています この例では実行時にCowを持つany Animalに produce()を呼んでいます この場合Cowのproduce()でMilkが戻ります MilkはAnimalプロトコルの関連型Commodityの 上限であるany Food内で格納できます これはAnimalプロトコルに 準拠する具象型には安全です
一方で関連型がメソッドか初期化子の パラメータリストに 現れた時を考えてみましょう ここではAnimalプロトコルのeat()メソッドが 消費位置に関連型FeedTypeを持っています メソッドを呼ぶために このタイプのパスが必要です 変換は逆方向なので 型消去はできません 関連型の上限存在型は 具象型が未知なので 実際の具象型には安全に転換できません 例を見てみましょう 再びCowを格納したany Animalがあります Cowのeat()メソッドはHayを受け入れます AnimalプロトコルのFeedTypeの 上限はany AnimalFeedです しかし任意のany AnimalFeedを考えると 具象型Hayを格納するという スタティックな保証はありません 型消去は消費位置では関連型とは 実行を許されません その代わりopaque some typeにパスして 存在型any typeを取り除かねばなりません 関連型との型消去の行動は Swift 5.6の既存言語機能に似ています 参照型の複製プロトコルを考えてください このプロトコルはSelfを戻す 単一clone()メソッドを定義し any Cloneableでclone()をコールすると 戻り値の型のSelfが上限に型消去されます Self型の上限は常にプロトコルそのもので 新しいany Cloneable typeが戻ります まとめるとプロトコルに 準拠する具象型を格納する 存在型をanyを使って宣言できるということです これは関連型のプロトコルでも通用します 生産位置で関連型プロトコル メソッドを呼び出す時 関連型は関連型と同じ制約を持つ 存在型の上限に型消去されます 具象型の抽象化は機能入力に便利なだけでなく 機能出力にも便利で 具象型は実装においてのみ可視できます では具象型を抽象化して その実装詳細からコードの 重要なインターフェイスを 分離させた上で変更において 静的型代入をモジュールで 堅牢にする様子を見てみます 動物に餌を与えるため Animalプロトコルを一般化します 動物はお腹をすかすと食べねばなりません Animalプロパティに isHungryプロパティを足します feedAnimals()メソッドは 空腹の動物に餌を与えます hungryAnimalsのサブセットの計算を hungryAnimalsプロパティに分割します hungryAnimals()の実装は filter()メソッドを使用し isHungryプロパティがtrueである 動物のサブセットを選出します any Animalの配列にfilter()を呼び出し 新しいany Animalの配列が戻ります お気づきかもしれませんが feedAnimals()はhungryAnimalsの 結果を一度だけ繰り返し この仮配列をすぐ破棄します これは大勢の空腹の動物がいると非能率的です この仮割付を避ける方法の一つは lazy collections機能を使用することです filterのコールをlazy.filterに置き換えると lazy collectionとして 知られる機能が得られます lazy collectionはfilterコールで戻る配列と 同じ要素がありますが仮割付を避けられます しかしhungryAnimalsプロパティの型が この複雑な具象型として宣言されねばならず anyAnimal配列のLazyFilterSequenceとなります これにより不要な実装詳細が露出されます hungryAnimalsの実装に lazy.filterを使ったことなど feedAnimals()は気にしません 繰り返せるコレクションを 得られるか知りたいだけです Opaque Result Typeで複雑な具象型を 抽象インターフェイスの後ろに隠蔽できます hungryAnimalsのクライアントは コレクションプロトコルに従う 具象型を得られるとはわかっていますが 実際 どの具象型かはわかっていません
しかしコード上ではクライアントから 静的型情報を隠蔽しすぎています hungryAnimalsがコレクションに従う具象型を 出力すると宣言していますが このコレクションの要素型は何も知りません この知識なしでは要素型はanyAnimalで 要素型に関してそのままパスするだけで Animalプロトコルの どのメソッドも呼び出せません ではOpaque Result Typeの some Collectionを見てみます 実装詳細の隠蔽と十分豊富なインターフェイスは 条件付きOpaque Result Typeで バランスを取ることができます 条件付きOpaque Result Typeは Swift 5.7の新機能です 条件付きOpaque Result Typeは プロトコル名の後の鉤括弧内に 型引数を入力することで実行できます コレクションプロトコルには 単一型引数の要素があります 条件付きOpaque Result Typeで hungryAnimalsが宣言されると 実際にはany Animalの LazyFilterSequence配列が クライアントから隠蔽されていますが クライアントは要素関連型が any Animalと同等のコレクションに準拠する 具象型であるという知識は持っています ここに相応しいインターフェイスです feedAnimals()のfor文の中で animal変数はany Animal型で 空腹の動物にAnimalプロトコルの メソッドを呼び出せます これはコレクションプロトコルが 要素関連型は主要関連型だと 宣言するのでうまくいきます このようにプロトコル名の後の 鉤括弧内で関連型を指定して 自身のプロトコルを主要関連型として 宣言できます 主要関連型として最適の関連型は 要素型など通常コーラーによって 提供されるもので イテレータ型のような 実装詳細とは対照的です よくプロトコルの主要関連型と このプロトコルに従う 具象型のジェネリックパラメータの間で コレスポンデンスがあります ここではコレクションの要素主要関連型が コレクションに準拠すると標準 ライブラリで定義される具象型の 配列とセットの要素ジェネリック パラメータで実行されます 要素のコレクションは someを使いOpaque Result Typeで 使用できます またanyで条件付き存在型としても使用できます Swift 5.7以前は特定の ジェネリック引数の存在型を 表現するには独自のデータ型を 書く必要がありました Swift 5.7はこの概念を条件付き存在型として 言語に組み込んでいます
もしhungryAnimalsにレイジーか熱心かで hungryAnimalsを計算するオプションを与えたら any AnimalのOpaqueコレクションを使うと 関数が2つの違った基底型を戻すエラーが出ます その代わりにany Animalの any Collectionを戻すことで APIがコールを通して違う型を 戻せると示しエラーを正せます 主要関連型を制約できることで Opaque Typeと存在型に さらなる表現度を与えます コレクションなど様々な標準 ライブラリプロトコルと使用でき 主要存在型を持つため独自の プロトコルも宣言できます Opaque Typeでのコード作成は 抽象型関係に頼らねばなりません 関連するプロトコルで 抽象型間の必須タイプ関係を 確認し保証する方法についてお話ししましょう この動物が食べるAnimal Feedの具象型のための 新しい関連型をAnimalプロトコルに追加します またeat()メソッドで動物に これを食べるよう伝えます 面白くするためさらにComplicationを 追加します 動物に餌を与える前に 適切なタイプの作物を育て 餌を生産するために作物を収穫します ここに最初の具象型があります Cow(牛)がHay(干し草)を食べます ですのでCowを考え まずHayを育てねばなりません 刈られて干し草に処理され Cowが食べることになる Alfalfaがあります これは2番目の具象型です 鶏(chicken)はエサ(scratch)を食べます 鶏(Chicken)を持ってきたら まずキビ(millet)という作物を育て(grow) 収穫(harvest)して処理し 餌(scratch)を生産し 鶏(chicken)に食べさせます この2つの具象型を抽象化させて feedAnimal()メソッドを一度実行させ 牛と鶏と共に 将来 飼うであろう様々な タイプの動物に食べさせます feedAnimal()は消費位置に関連型がある Animalプロトコルの eat()メソッドと行動するため feedAnimal()はsome Animalを パラメータ型として 受け取ると宣言して存在を取り外します まず AnimalFeedとCropの 2つのプロトコルを 既にわかっているプロトコル と関連型で定義します AnimalFeedにはCropに準拠する 関連型CropTypeがあり CropにはAnimalFeedに従う 関連型FeedTypeがあります プロトコルの型パラメータの 図表で見ることができます まずAnimalFeedを見てみます どのプロトコルにもSelf型があり 具象準拠型を表します このプロトコルには関連するCropTypeがあり Cropに準拠します 関連型CropTypeには ネスト関連型FeedTypeがあり AnimalFeedに準拠しており Cropに準拠するネスト関連型 CropTypeがある… と続きます 実際 これはネスト関連型で 永遠に続き AnimalFeedとCropの準拠を繰り返します
Cropプロトコルも1シフト しただけの同じ状況です Self型で始まりCropに準拠し AnimalFeedに準拠する関連FeedTypeがあり これにはCropに準拠する ネスト関連型CropTypeが…
と永遠に続きます これらのプロトコルが具象型間で正しく関係を 構築するか見てみます 動物に餌をやる前に 農作物を育て 正しい餌を生産するため処理せねばなりません grow()はAnimalFeed プロトコルの静的メソッドで 型がAnimalFeedに準拠する特定値ではなく AnimalFeedに準拠する型に 直接呼び出されねばなりません AnimalFeedに準拠する型の 名前を書かねばなりませんが 別のプロトコルである Animalに準拠する型の特定値しかありません Animalに準拠する型であるこの値の型は 得られます そしてAnimalにはAnimalFeedに準拠する 関連型FeedTypeがあります
この型はgrow()メソッドの 基盤として使用できます AnimalFeedのgrow()メソッドは AnimalFeedのネスト関連型 CropTypeである値を戻します CropTypeはCropに準拠するので harvest()を呼び出せます でも何が戻るでしょう? harvest()はCropプロトコルの関連型FeedTypeを 戻すために宣言されます この場合 コールの基盤は (some Animal).FeedType.CropTypeで harvest()が出力する型の値は (some Animal).FeedType .CropType.FeedTypeです 残念ながら違う型です (some Animal)のeat()は (some Animal).FeedTypeを予期し (some Animal).FeedType.Crop Type.FeedTypeではありません このプログラムはよくありません これらのプロトコルの定義は もしanimal feedの型で始め 作物を育てて収穫しても 動物が食べるであろう 同じ型の餌が 戻るという保証はありません また もう一つはこれらのプロトコルの定義は 一般的すぎて 具象型間の 望ましい関係を正確には表していません これを理解するために HayとAlfalfaを見てみます Hayを育てるとAlfalfaが得られ Alfalfaを収穫するとHayが得られ… と続きます コードをリファクターするとして Alfalfaのharvest()メソッドの戻り値の型を HayではなくScratchに間違えたとします この間違いの後 栽培と収穫で最初と同じ餌を生産できるという インバリアントを破ったにも関わらず 具象型はまだAnimalFeedとCropプロトコルの 必要条件を満たしてしまっているのです AnimalFeedプロトコルをもう一度見てみましょう ここでの実際の問題は 別個の関連型が多すぎるということです これらの関連型の2つは同じ具象型だと 表さねばなりません そうすれば間違った具象型が プロトコルに準拠するのを防げ feedAnimal()メソッドに 保証を与えられます これらの関連型間の関係はwhere句で書かれた same-type requirementで表現できます same-type requirementは2つの違った ネストでもあり得る関連型が 同じ具象型でなければ ならないという静的保証を表します same-type requirementを追加することで AnimalFeedプロトコルに準拠 する具象型に制限を課します このsame-type requirementでは Self.CropType.FeedTypeは Selfと同じだと宣言しています ダイヤグラムではどうでしょうか? これで視覚化してみましょう AnimalFeedに準拠する 具象型にはCropに準拠する CropTypeがあります しかし このCropTypeのFeedTypeは AnimalFeedに準拠する別の型ではなく 元々のAnimalFeedと同じ具象型なのです 永遠に続くネスト関連型の代わりに すべての関係を1つのペアに畳み込み 同族関連型にしました ではCropプロトコルはどうでしょう? CropのFeedTypeを 1つのペアに畳み込みましたが まだ1つ余分な関連型があります CropのFeedTypeのCropTypeは 最初に始めた時のCropと 同じ型だと言いたいのです
これら2つのプロトコルに same-type requirementsが付き feedAnimal()メソッドに再び戻ります 以前同様some Animalから始めます そしてFeedTypeを得ます これはAnimalFeedプロトコルに準拠します この作物を育てるとsome Animalの FeedTypeのCropTypeが得られます しかしこの作物を収穫すると ネスト関連型の代わりに 動物が欲するFeedTypeを得ることができ ハッピーな動物が私たちが育てた 正しいAnimalFeed Typeを eat()できると保証されます 最後にAnimalプロトコルの 関連型を見てみましょう これまで見てきたものすべてがまとめられます
ここに2つの準拠型があります まず CowとHayとAlfalfaがあり 次にChickenとScratchとMilletがあります これら3つのプロトコルが3つの具象型間で きっちりと関係を作り上げていますね データモデルを理解すれば same-type requirementsを使い これらのネスト関連型の等価を定義でします プロトコル要求事項へのコールをつなげる時 ジェネリックコードは これらの関係に依存できます このセッションでは型消去が安全で 型関係が保証される コンテキストを探求しました そしてリッチタイプの維持と Opaque Result Typesと 存在型で使える主要関連型を使った 実装詳細の隠蔽との正しい バランスについて話しました 最後に関連づけられた型のセットを表した プロトコルを通してsame-type requirementsを使って 具象型のセット間の型関係を 確認し保証する方法を見ました ありがとうございました WWDCをお楽しみください
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。