今回も引き続き オプショナルバインディング
を使用していく上でのレパートリーを広げるみたいな気持ちで話を進めていくことになりそうです。これまでに脱線しながら話してきたことが改めて登場してくる流れになるので、再確認しつつそこから想いを巡らせたりして、おさらいしていこうと思います。よろしくお願いしますね。
今回はゆめみ社外の人への一般公募はなかったようなので、基本的に社内メンバーのみでの開催になりそうです。
———————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #160
00:00 開始 00:47 今回の展望 01:19 Any 型と無制約な型パラメーターとの違い 02:30 透過的に型を採るか、Boxing するか 03:20 無制約と Any での束縛とは同等 04:46 どこで再定義されたかを確認する方法 06:25 オーバーロードの適用順位 08:11 プロトコルの継承を伴うオーバーロードの適用順位 10:11 @_disfavoredOverload による適用順位の変更 13:57 同じメソッド署名でオーバーロード 14:52 引数リストが正確にマッチする方が呼ばれる 16:00 @_disfavoredOverload を使ってみる 17:17 @_disfavoredOverload の方を選んで呼び出すことはできない 19:06 同じ署名があるかが変化するときに威力を発揮 20:08 import と @_disfavoredOverload 20:49 型には @_disfavoredOverload は使えない 23:35 属性名からして型には使えなそう 23:53 バックポートに対応する例についてのおさらい 26:30 オーバーロードのスコア付け 27:58 推論解のスコアによるオーバーロードの解決 28:57 優先順位の決定と、順位が定まらなかったとき 30:56 クロージング ————————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #160
では始めていきましょう。本日はオプショナルバインディングについての解説を引き続き行います。前回は思いがけずオプショナル周りのモデルと補完構文、あとプリント文やエニー型で扱う機能、警告の話をしました。この勉強会でもその話をしたことがあるので、知っている人もいるかもしれませんね。今回はその続きを話していきます。
前回、議論の中で勘違いしている部分が多かった印象がありました。実際、最後の方でエニー型とジェネリクス周りについて勘違いがあり、試してみたところ思ったのと違う動きをしたことがありました。そのため、まずその勘違いについてお話しします。
具体的には、ジェネリクスの制約がないものと型のエニーが同等だという話をしていましたが、それが勘違いでした。実際にはそうではありませんでした。
例えば、func something(any: Any)
という関数と func something<T>(t: T)
という、ジェネリクスTで何の制約もかけていない関数の2つを比べた場合、よく考えれば異なることがわかります。この2つが同じだと思っていましたが、コンパイル時には明らかに異なる動きをします。
エニー型を取る場合と、コンパイル時に具体的な型を取る場合では大きく違います。ジェネリクスの利点は、型を透過的に扱えることです。この点を忘れてしまいがちですが、よく考えれば当然のことです。
例えば以下のようなコードを考えてみてください:
func something(any: Any) {
// 何でも受け取る
}
func something<T>(t: T) {
// ジェネリクスで何でも受け取る
}
上記の2つの関数は同じ名前ですが、異なる型引数を取るため、コンパイル時に明確に区別されます。この点で勘違いがあり、混乱してしまいました。
また、コンパイルエラーの中でどこで再定義されたかが表示されることがあります。これは非常に役立つ情報なので、再度確認しておきましょう。
このように、エニー型とジェネリクス型の違いを正しく理解することが重要です。これにより、再度同じようなミスを防ぐことができます。
以上が、前回の勘違いについての話です。この後はオプショナルバインディングの詳細な解説に戻りますが、まずはこの点をしっかりと理解しておきましょう。 さて、前回の勉強会で話していた具体的な型とジェネリックパラメータについてですが、この2つが同期してオーバーロードされるという現象は普通に存在します。実際にエニー型を取る場合もあれば、ジェネリックとしてどんな型でも取る場合もあります。このようなときに、どちらの関数が呼ばれるのかと気になることがありますよね。
この点について昔調べてみたところ、シンプルなルールが存在します。より制約の厳しい方が優先されます。つまり、制約に合致する方が優先的に使われるということです。
例えば、Any
型の値があるときに関数を呼ぼうとすると、渡された値はジェネリック型のT
にも当てはまるし、Any
型にも当てはまりますが、このときは前者が選ばれるのです。例えば、Any
型を返すようにすると、Any
型を想定した関数が実行されますし、具体的な型が当てはまる場合はジェネリックの関数が呼び出されます。
プロトコルとジェネリクスの組み合わせはもう少し複雑な制約が絡んできます。例えば、プロトコルAとプロトコルBがあり、BがAを継承している場合、ジェネリック型パラメータB
がA
であるとき、または型パラメータがB
であるときなどが考えられます。この場合、特定の型がプロトコルAに準拠しているとき、その型に対して最も制約が近いB
が優先されます。
具体例を挙げると、Int
型がプロトコルAおよびBに準拠している場合、最も具体的なメソッドが呼び出されます。従って、一般的なものから順に制約が厳しいものが優先され、最後にAny
型が選ばれるという流れになります。
もし特定のメソッドの優先順位を変更したい場合、アノテーションを使う方法もあります。アノテーションを付与することで特定のメソッドを優先させることができます。しかし、詳細については再度確認が必要です。
例えば、Int
型をAny
型として扱いたい場合、型変更を行うと全てがAny
型として処理されます。同様に、プロトコルAとして扱う場合も、具体的な制約に従ってメソッドが実行されるなど、アノテーションの使い方によって処理が変わってきます。このように、具体的な条件やアノテーションの付け方によってメソッド呼び出しの優先順位が変わることを覚えておくと良いでしょう。
最後に、アノテーションの細かい使い方についてはもう少し調べてみる必要があるかもしれません。皆さんも何か気になることがあれば、ぜひ調べて共有してください。 調べてみると、Twist Eneluxアノテーションについての情報は特に見つかりませんでした。現時点ではこのアノテーションが具体的にどのような場面で使われるのかは不明ですが、新しいOSで提供されたAPIを自前でバックポートする際に利用されることがあるようです。
例えば、古いバージョンのOSではバックポートされたAPIを使い、新しいバージョンでは公式のAPIを使いたい場合に、このアノテーションを付けて区別するという話がありました。これは、古いバージョン向けに自前でバックポートをする場合には非常に便利な手法です。
最近バックポートされた有名なものについても話題に上がりましたが、具体的にどれが該当するかはわかりませんでした。ただ、公式のバックポートではなく、自分のプロジェクト内でアノテーションを適用するという点で少し混乱がありました。
同じ名前でオーバーロードしたい場合に、アノテーションを付けることで優先度を制御することができるという点も議論されました。例えば、デフォルトパラメータを持つ関数とそうでない関数があった場合、呼び出し元でどちらの関数が呼ばれるかをアノテーションで明確に区別できることが重要です。
具体的に優先度が異なる関数を定義して試してみましたが、実際に動かしてみると期待通りに動作しないこともありました。それを確かめるためには、どの関数が呼ばれているかをデバッグして確認する必要があります。特にバックポートやオーバーロードの優先度を操作する場合は、細かい動作確認が不可欠です。
以上の話から見ても、アノテーションの使い方一つでソフトウェアの挙動が大きく変わる可能性があり、非常に興味深い分野だと言えます。今後もさらに詳細を調べる必要がありますね。 とりあえず消してみるとどうなるのでしょうか。両方ともインポート通りになるのかな。あれ、どうなんだっけ。ここも関数を入れた時点で優先されないということなのでしょうか。これはどう呼ぶんだろう。誰か呼べる人いますか。独自のものが出た状況で呼べなくていいのか、それともそんなわけないよね。試しに名記してみます。
まず、ここで「イン」とやってみてもダメですね。今ひとつ分からないな。前に言ったような感じで、実装版は環境交流を設置して実際の環境を作ると、様々な実装が必要になってくるかもしれませんね。
なるほど、ローカルで所管APIが揃っているのではなく、インポートする時とか。そうですね、インポートするとき、例えばLinuxでセルチャーを実行するときに寄せられないものをアプリとして、それを決めて実装しますけど。
iOSとかMacの場合は用意しておきながら使いますからね。つまり、所管APIは用意しておくけど、本物があるときにはそっちを使ってね、みたいな感じですね。なるほど、こういうところは言葉で話していても難しい可能性がありますので、具体的にモジュールを作ってみましょうか。
これで例えばモジュールAを作ります。ここではないですね、こっちですね。これで例えばフレームワーク、モジュールAとか作って、これでAPIを簡単につけてみたいからちょっとやってみましょう。では、トラックとパブリックストラクトでAPIを定義します。
public struct API {
public func action() {
print("公式機能を実行")
}
}
そして、これをビルド通すとやっぱりビルドできるよね。簡単に返してくるのかな。次に、別のモジュールでインポートします。モジュールAをインポートして、APIを使ってみます。
import ModuleA
let api = API()
api.action()
これでビルドを実行すると、プリントしないとダメですね。print(api.action())
としてみましょう。これでビルドすると「公式機能を実行」と表示されるはずです。
モジュールのデプロイターゲットなしでも動作確認できますね。ただ、まだエラーが出る場合は、グローバル関数にしてしまってもいいかもしれませんね。例えば、APIに直接関数を置かずにグローバル関数として定義する方法です。いろいろ試してみながら、ややこしい部分を解消しましょう。
public func performAction() {
print("公式機能を実行")
}
これで performAction()
を呼び出して実行することでも確認できます。
全体として確かにオーバーロードとは言わないですね。関数のオーバーロードと混同しないように注意しましょう。注目するべきはメソッドの署名やモジュールのインポートの部分かもしれません。次回はより具体的なモジュール間のやり取りや依存関係についても触れていきたいと思います。 なので、こういう形にして基本的にはできているのかなと思われます。ここで本来だったら「public」や「official」といったキーワードが出てくる流れになりますが、若干未確認の部分があるので、このまま終わりにします。しかし、仮に環境によってはOSのバージョンやプラットフォームの違いによってモジュールAが提供されていたとしても、その中のアクションメソッドが存在しない可能性があります。そういった場合でも、アクションメソッドが搭載されていればその機能を使いたいし、将来性を考慮して使った方が良いかもしれません。しかし、存在しないものは仕方ありません。
こういった場合には、新しい関数を作成するよりも、本来提供されるであろう機能と互換性のある独自の機能を実装しておくと良いです。インポートしたモジュールAにアクションメソッドがなかったとしても、ちゃんとアクションメソッドとして動作するようにできます。ただし、この実装方法だと、一番近い名前空間にある機能が利用されるので、将来的にモジュールAにアクションメソッドが標準搭載された場合には、その標準のメソッドが優先されることになります。そのときに、さっき教えてもらった方法が生きてくるという感じです。
実行しながら試したいのですが、フレームワークがうまくリンクできなくて試せなかったです。ただ、これは基本的にありえます。意味がよく分からないという人がいるかもしれませんが、モジュールを作成して独自のアプリで試してみると、差異を理解しやすくなるかもしれません。
コメントで面白い話があります。オーバーロードのスコアというものがありますね。確かにそういったスコアも意識されているようです。これも見てみますが、せっかくなので紹介しておきます。イーターで公開している有名なメタルさん、その方も名前を聞くだけで分かりますよね。コンパイラー関係の人なのかなと思います。オーバーロード選択のスコア規則だとかですね。スコアがどういう風にあって、どのマッチ具合点数で優先されるのか、という風に定義されているんですね。
これはサブタイプのオーバーロードのスコアとも言われていますが、どういう優先順位で選択されているのかを見ていくと、理解が深まって混乱しにくくなるかもしれません。興味がある人にお任せしますが、いずれにしても具象型が優先されます。サブタイプがあればサブタイプが優先され、それ以外の場合はプロトコルと似た優先順位で扱われます。具象型の優先があり、その後にジェネリックパラメーターの制約があります。それでも優先度がうまく定まらない場合には曖昧とされます。
たとえば、プロトコルAとプロトコルBが全く関係ない状況で、Int
型がAとB両方に準拠している場合、曖昧になります。この場合、どっちを使うか明記してくださいという感じになり、特定のプロトコルを指定するとそちらが選ばれる、という仕組みです。
これはアノテーションでしょうか。たぶんアノテーションですね。このあたりも確認してみると面白そうです。今回、少し時間がかかってしまいましたので、今日はこれくらいにしておきましょう。また次回に引き続き勉強していこうと思います。
お疲れ様でした。ありがとうございました。