Page icon

第69回

2022/01/21
番外編
Empty
Empty
Empty
12 more properties
今回は、前回に見た
共変性と反変性
の話を受けて、その考え方で見渡してみると窺いやすい気がするクラスやプロトコルの特徴みたいなところを眺めていってみます。それを見終えた後は、これまでの
A Swift Tour
に戻って続きの
Error Handling
を見ていくことにしますね。どうぞよろしくお願いします。
—————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #69
00:00 開始 00:39 共変性と反変性 01:50 配列型における共変性 02:12 関数型における共変と反変 02:27 関数型における戻り値の型の関係性 04:19 関数型における引数の型の関係性 05:15 プロトコルにおける共変に関する特色 07:09 クラスにおける共変に関する特色 09:00 共変性や反変性を踏まえたオーバーライド 11:20 プロパティーにおける共変に関する特色 12:30 共変性という言葉が窺える場面 13:46 共変と反変が同居する機能 17:50 イニシャライザーにおける共変に関する特色 18:31 失敗可能イニシャライザー 22:23 RawRepresentable 23:41 失敗可能イニシャライザーが失敗するはずのないとき 25:04 失敗可能イニシャライザーのオーバーライド 26:11 クロージング 27:33 次回の展望 ——————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #69
はい、えっと、今日やっていくことは前回の話の続きです。共変性と反変性、このあたりを実際にどんな場面で使われているのか見ていこうかなと思います。まず、軽くおさらいをしていく感じにしようかなと思います。
まず、反変性と共変性がどういったものだったかというと、ある型とそのサブタイプがあったときに、コンテナBがコンテナAのサブタイプになるというのが共変性です。逆に、コンテナAがコンテナBのサブタイプになるというのが反変性です。この言葉を聞いて前回の話をイメージすると、なんとなくわかるかと思います。
具体例を挙げると、例えば、プロトコルでもクラスでもどちらでもいいのですが、クラスAがあって、それでクラスBがAを継承しているような状況です。配列の例がわかりやすいですね。配列Aがあって、その中に配列Bのインスタンスが入るという性質が共変性です。
それに対して反変性は、関数などで現れる特性です。共変性の配列を作るとき、関数の戻り値が共変性になります。例えば、クラスAをコンテナの要素として持つ戻り値を返す関数では、戻り値としてクラスBを返す関数として描くことができる、というような感じです。
実際には、たとえば次のような状況を考えます。ある関数が定義されていて、引数なしで戻り値が配列Bのときに、その関数に配列Bを渡しても曖昧だと言われてしまうことがあります。例えば、配列AにBを渡す場合、逆に配列BにAを渡すことも問題になります。これが共変性です。
次に、プロトコルの共変性と反変性についてもう少し詳しく見ていきます。たとえば、プロトコルAがあり、そのプロトコルに準拠するクラスが定義されている場合の話です。
protocol A { func method1() -> A func method2(param: B) }
このプロトコルに準拠する具体的なクラスを作ると、共変性と反変性の関係を意識しなければいけません。つまり、プロトコルの戻り値部分でBを返し、引数部分でAを受け取る関数を書くといった具合です。実際にこれを実装するときに、共変性や反変性が適用され、準拠していないとエラーになることがあります。
プロトコルにおける共変性と反変性を理解することは、Swift全体の設計や実装において非常に重要です。概念的な部分をしっかり押さえておくことで、さらに柔軟で強力なコードを書けるようになります。 ただ、これがプロトコルではなくクラスにすると、うまくいくことがあります。クラスになると、共変性と反変性がちゃんと生かせるんですよ。それがちょっと面白くて、今は構造体とプロトコルになっている部分をクラスにして、オーバーライドを使うとどうなるのかを見てみました。エラーになっているものをクラスに変えて、継承することでオーバーライドが必要な状況になります。必要な実装を記述して、例えば
A
を返すのと、オーバーライドした
someMethod
で親クラスの
someMethod
を呼び出す形にします。そして
override func someMethod
のように書いて、適切に
B
を返す形にします。こうするとビルドが通ります。クラスの場合、共変性と反変性を十分に踏まえたオーバーライドができるという面白い特徴があります。
これが便利になるかどうかは実際のところですが、オーバーライドができることでかなり便利にコードが書けるようになります。ただ、オーバーライドをする際に若干難しそうに感じるかもしれません。しかし、関数単体で見ていたものと同じで、共変関係にあるものを戻り値として受け取れる関数でオーバーライドできることや、引数で反変関係にあるものを受け取れることを考えると、それほど難しくありません。実装の際に
B
を返せる話や、パラメーター
A
を取れる話も、共変性や反変性を踏まえれば自然なものとなります。
プロパティについては、共変性や反変性を考慮することがありません。具体的には、あるプロパティ
var
があったとき、それを共変関係にあるものや反変関係にあるものとして使おうとするとコンパイルエラーになります。例えば、クラス
B
のプロパティを
B
型として扱おうとする際には、計算型プロパティとして実装しないといけません。ゲッターとセッターを使って実装すると、親クラスにあった
A
型のプロパティを
B
型に規定することはできません。エラーメッセージには「協変性(covariant)」という言葉が出てきます。これによりSwiftが共変性や反変性を踏まえた言語仕様になっていることがわかります。
計算型プロパティにおいて、ゲッターは関数的な存在であり共変性を示します。一方でセッターはメソッド的な存在であり反変性を示します。そのため、プロパティに共変性や反変性を持たせることができません。オーバーライドするプロパティをサブタイプの関係にある
B
に置き換えることができない理由も理解できます。
イニシャライザーも同様に共変性を示すことがあります。ただ、共変性を示すのは特定の状況のみです。クラス
B
のイニシャライザーが
A
型を返すことはできませんが、失敗可能なイニシャライザー(
init?
)であれば、自身の型ではなくオプショナル型を返すことができます。例えば、クラス
B
のイニシャライザーが
init?
であれば、
B?
型を返すことができます。これにより、自身の型ではなくオプショナル型を返す機会が得られるのです。
要するに、共変性と反変性の概念を理解して活用すれば、Swiftでのオーバーライドやプロパティの扱いが自然に感じられるようになります。ただし、イニシャライザーに関しては共変性や反変性の示す機会は限られています。 とりあえず、こういうふうに
init?
init
っていう形で利用できるという話です。これはファンクションで受けてみちゃえば分かりやすいですね。つまり、
init(_:)
B
を返す関数、それを
B?
を返す関数で受けるって感じです。こういった使い方ができるので、このパターンはよく見ますね。
以前の勉強会でもお話ししましたが、
RawRepresentable
というプロトコルは元々
RawValue
を受け取ってインスタンス化する
initializer
を規定しています。しかし、必ずしも
RawValue
が対応する型を表現できるとは限らないため、
failable initializer
(失敗可能イニシャライザ)になっていますね。でも、もし
RawValue
がその型そのものを表現できる場合には、失敗させる必要がないわけです。
例えば、
RawValue
Int
型で、その型そのものが
Int
型で全て表現できる場合、そのような場合に
failable initializer
をわざわざ使うのはナンセンスです。
RawValue
でインスタンスそのものを表現できるとき、要は失敗するはずのないときには、
failable initializer
じゃなくて
initializer
を使うべきです。そうすることで、不必要に冗長なAPIを提供しなくて済むという利点があります。
このプロパティ
value
Int
型なら、全ての
RawValue
を表現できるじゃないですか。こういった場合に
init?
を実装するのはナンセンスです。
init
RawValue
を受けちゃえば良い感じになります。
クラスでも同じことが言えます。もしクラスAが
failable initializer
を持っていて、そのクラスを継承したクラスBは
failable initializer
initializer
としてオーバーライドすることも可能です。親では失敗する可能性があるけれども、継承したクラスでは失敗しない場合、
failable initializer
をオーバーライドして
initializer
として提供できるわけです。
今日の勉強会で言いたかったところは、共変性と反変性という概念を理解しておくと、それによってより冗長性のない、適切で分かりやすいAPIを提供していけるという話でした。
これで今日の勉強会を終わりにしますが、次回はエラーハンドリングのお話をメインにする予定です。その前に少し
init?
init
にしたときの利点なども紹介したいと思います。それでは、時間もちょうどいいので、今日の勉強会はこれで終了にします。