https://youtu.be/YUUgYi0u0W4
今回は、前回に続いて 定数と変数
の 型注釈
を最終確認して、時間があればその次の 変数の命名
について眺めていきます。型注釈についての印象を確認したり、変数の名前の付け方についての基本的なところを再確認していく回になりそうですので、どうぞよろしくお願いしますね。
———————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #94
00:00 開始 00:12 今回の展望 01:17 型注釈 02:51 as による型注釈 03:35 リテラルに対する as 05:41 Objective-C ブリッジに対する as 07:16 リテラルでの型注釈の挙動を窺う 10:01 アップキャストと型注釈 14:01 unsafeBitCast によるキャストの挙動 16:51 参照型とリテラルの as は型注釈と呼べるかどうか 19:54 Objective-C ブリッジと型注釈 21:16 動的キャストと型注釈 22:48 The Swift Programming Language での言葉の扱い 30:00 型パラメーターと型注釈 32:41 ここまでに話したいろいろな型注釈 33:29 型注釈をする機会はあまりない 34:22 初期値の提供と型推論 35:16 戻り値のオーバーロードと型注釈 37:52 引数を用いて戻り値を決めるガイドライン 41:03 クロージング ————————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #94
はい、じゃあ今日は引き続き、というかおさらいというか、型注釈についてのお話をやってから、その後、変数や定数の命名について話す回になりそうです。型注釈については前回と前々回にお話ししましたし、変数の命名についてはずいぶん前のAPIデザインガイドラインの回でやりました。ですから、今日はおさらいが中心になりますけれど、何度もおさらいすることは効果があると思うので、ゆっくり進めていきましょう。新しい気づきがあったら、ぜひ教えてください。役に立つと思います。
まず型注釈について話していきましょう。今表示中のスライドは前回お話した内容なので、とりあえずスキップしても大丈夫です。特筆すべきところは、前回話したとおりです。var
を使った型注釈や型推論について、前回触れましたが、少し話し切れていない部分があったので、そのあたりも補足しておきます。
型注釈
という言葉をあまり使わないという話を前回しました。そして、実際にどう書くのかを具体的に見て、「これを型注釈と言うの?」という議論もしました。TypeScript
の記事を見たり、話し合う中で問題ないだろうという結論に至りました。
型推論もありますので、注目されている場合があります。型を書かなくてもString
型になる場合でも、書いておいた方が分かりやすくなることがあります。そんなときに型注釈という言葉を使うと、何を言いたいのか分かりやすくなります。
また、もう一つの型を明記する方法としてas String
という方法もあります。これも型注釈と言っていいのか、個人的にはそう思いますが、型注釈として使える場面は限られてきそうです。そのため、as
についてもこの勉強会で何度も見てきたので、復習も兼ねて話しておきます。
例えばリテラルに対するas
の時には、型注釈と言って問題ないのではないかと思います。この辺りどうでしょうか?印象的に違和感があるようなら教えてください。例えば、NSString
からas NSString
なども含まれますが、リテラルに対するものだと型注釈っぽい雰囲気があります。
型注釈とas
の具体的なコード例も考えてみましょう。例えば、以下のようなコードです:
let string: NSString = "Hello, world!" as NSString
NSString
型として宣言しています。これが型注釈プラスas
のパターンです。暗黙のキャストが必要な場合もありますね。以下のような場合、
let string = "Hello, world!" as NSString
型注釈がプラスされることで、明確にNSString
として扱われます。このような処理が通過するかどうか、コンパイルタイムにはas
がどう処理されるか把握していない場合があります。場合によってはキャストが明示的に必要になります。
例えば、String
型の文字列をas NSString
とする例もそうです。キャストが明示的なキャストで必要となります。この辺りの処理も理解することで、型注釈やキャストがどのように動作するかを把握するのが大事です。
今日はここまでにしておきましょうか。次回は変数や定数の命名について進めていきましょう。何か質問があれば自由に聞いてくださいね。 やっぱり、ちょっと特別な感じがしますね。ここで注釈をあくまでも「Modulated Literal」のデフォルトで受ける型がString
だけど、それをNSString
に変えることができます。別にas
をしているわけではありません。何かの処理をしているというよりは、あくまでも型を明示的に注釈しているだけです。
4
ではそんな感じです。1
とかだと、やっぱり処理がさらに型を注釈以上の何かの処理が走っている感じがするので、色々と処理が走っていそうな気がしますね。この3行目、なるほど。最適化とかでもしかしたら単純に型がString
になっているだけかもしれないですけど、ちょっと取り留めのない話になってしまいます。このあたりを表側から確認できたら面白いですね。何かありますかね。
Literal
のasCast
で完全に渡せないところがひっかかるんですよね。NSString
をパラメータで受けるような場合にはas
をしなきゃだめじゃないですか。それと、あ、そんなことないか。文字列にLiteral
渡せますか?リテラルは渡せます。じゃあ、一緒のことですね。分かりませんね。文字列にリテラルに対しては確かに問題ないです。もちろん、間違ったキャストをするとエラーになりますが、正しいキャストを書けば通過します。
これは省略されているイメージを持ってもいいかもしれないですね。アップキャスト可能なものは型注釈できる、ということですね。型注釈可能なものは、まずビックリマークやクエスチョンマークがつかないものですね。なるほど、型注釈ができると言えます。
その感覚についてですが、クラスベースとサブクラスで議論します。ベースとサブがあって、インスタンスがサブとして、as Base
とする場合、これは率直に言えばアップキャストですよね。これでインスタンスコロンベースもできますよね。それで、サブクラスがベースクラスとして認識される。つまり、アップキャスト可能なものは型注釈も可能ということです。
型注釈可能なものは、基本的にアップキャストもできます。バック型注釈という言葉を使ってもいいかもしれません。型注釈可能なものはアップキャストが基本的にできますし、できないパターンはあまりありません。型を注釈することで、その型として認識されます。
この話を整理すると、最初に言っていた「同じもの」として捉えて問題ありません。型注釈とアップキャストはほぼ同じ動作ですね。これを頭に入れておけば、Swiftの型システムを理解しやすくなります。
オブジェクト指向のインスタンスの場合、アップキャストしてもインスタンス自体に変化はありません。型注釈と見なしても問題ありません。この2つ、いわゆる16行目と17行目の同じ動きです。コンパイラが理解する型として認識され、型のメタタイプを振り返るだけなので、全く問題ありません。
内部的には、例えばUnsafeBitCast
を使ってキャストすると、サイズが違う場合にランタイムエラーになります。同じサイズであれば、間違った型にもキャストできてしまうこともあります。ベース型のインスタンスをサブ型に変える場合も、その危険性があります。ランタイムではベース型であるため、サブ型のメソッドを持つ場合、ここでエラーが発生するのがUnsafeBitCast
の危険なところです。
例えば、インスタンスがサブ型として認識されている場合、ベース型に振り返っても全く問題ありません。ですが、UnsafeBitCast
の場合、間違ったキャストをすると、ランタイムでエラーが発生します。これがSwiftの型システムとそのコンパイラ処理の基本的な動きです。 次に、Swiftのas
およびas!
、as?
の使い方について詳しく見ていきましょう。as
は単純なキャストを行うためのオペレーターです。一方で、as!
やas?
はダウンキャストを行うためのものです。ここでのキャストは、例えば、ある変数がもともとはある型を持っているが、それを別の型として解釈したい場合に用いるものです。
例えば、下記のコードを考えてみましょう。
let string: String = "Hello, World!"
let nsString: NSString = string as NSString
ここで、string
はSwiftのString
型ですが、NSString
型にキャストしています。この操作はObjective-C
とSwift
のブリッジを介して行われるため、あまり型注釈とは言いづらいですね。ただ、ソースコードレベルでは型注釈として見なすことも可能です。
次に、ダウンキャストの例を見てみます。
let anyValue: Any = "Hello, World!"
if let stringValue = anyValue as? String {
// stringValueはString型として扱われる
}
as?
はオプショナルキャストを行い、キャストが失敗した場合はnil
を返します。一方、as!
は強制キャストを行い、キャストが失敗するとランタイムエラーを引き起こします。
let anyValue: Any = "Hello, World!"
let stringValue = anyValue as! String
このように、キャストの方法によってランタイムの挙動やコストが変わる点が興味深いです。
さて、「型注釈」と「キャスト」がどう違うかについて話しましたが、まとめると、型注釈は通常コンパイラが型を明示的に決定する際に使われ、キャストはランタイムで型の変換を試みるものです。この違いが理解できると、Swiftの型システムの使い方がより明確になります。具体的には、以下のコードのように、キャストにはリスクやコストが伴うことがあるため、慎重に扱う必要があります。
let unknown: Any = "Some String"
if let specificString = unknown as? String {
print(specificString)
} else {
print("Failed to cast")
}
このように、キャストの方法やタイミングを理解することで、より安全で効率的なコードを書くことができます。 話が分散されていますが、意外と後半だったような気がします。どこかにシンプルなas
の話が出てきていたと思います。ゆっくりまた探してみましょう。
タイプキャスティングについての話ですが、Expressions
のところにas
型キャスト演算子としてタイプキャスティングオペレーターの説明があります。その前のセクションではあまり触れられていなくて、タイプキャスティングによるスーパークラスやAny
型へのアップキャストについては書いてありましたね。
タイプキャスティングの最後の方では、スーパークラスやAny
型へのアップキャストが書かれていたと思います。この部分は本の後半にありますが、具体的にはネットビジネスに関連するタイプの一つ前のセクションにあります。数ページ前に、ブリッジキャスト、Any
型へのアップキャスト、スーパークラスへのアップキャストの3種類が紹介されていないか確認してみました。もしかしたら、私のメモが間違っていたかもしれません。
以前読んだ本では、スイッチ文で0 as Int
のようなコード例がありました。スイッチ文の例が書かれたページを確認しましたが、スーパークラスへのキャストが見当たりませんでした。本が古い可能性もあります。現在手元の本はバージョン5.4ですが、新しいバージョン(5.5)では改善された内容かもしれません。いずれにせよ、バージョン5.4まで確認しています。
また、最近出版された本に関しても確認しましたが、個人的な印象ではアップキャストについて触れていた記憶があります。何かセクションが移動しているのか、他の場所に移動したのかもしれません。5.6に関しても確認しましたが、載っていなかったため、自分で追記しただけの可能性もあります。もしくは、昔の場所に載っていて、現在は別のセクションに移動している可能性も考えられます。
他にも関連する本をめくって確認してみましたが、プロパティラッパーの話なども出てきました。意外と高度な内容ですが、ダイナミックコーラブルについては触れられていなかったりします。ユーザーが求める内容に応じて編集されているのかもしれません。
とりあえず、asCast
がなかなか出てこないので、現状ではこれくらいにしておきましょう。asCast
とas?
やas!
の取り扱いについて異なる部分がありますが、型アノテーションと呼べるのは7行目くらいまででしょうか。
言及された型アノテーションについて検索して何が出てくるか見てみたところ、TheBasics
セクション以外にもジェネリクスのセクションなどで話題に上がっています。具体的には、タイプパラメーターを特定すると型アノテーションをしたような感じだと言及されていました。例えば、Functionでの使用についてval
をElement
のようにした場合、Element
が型アノテーションされたようなものだと解釈しています。ただ、この説明は少しわかりにくいかもしれませんが、型パラメーターを特定することで型アノテーションのように扱えるという話ですね。
以上のような内容で、型パラメーターを使用する場合についてもアノテーションの役割を果たすことが分かりました。 なるほど、これも型注釈と捉えても良さそうな感じですね。あと他に、as
については大体言いましたね。リテラルのas
、アップキャストのas
、ダウンキャストのas
、それと互換性を加味したas
。うん、これは違うな、これは違う。あとは、Objective-C Bridge、要はブリッジのas
といったところでしょうか。
確認が長くなりましたが、そうですね、そんな話をしたよという振り返りでほとんど時間が終わってしまいましたが、先へ進みましょう。ここがちょっとお話ししたかったところで、Swiftの本の中に「実際に型注釈を書く機会はあまりない」と書いてあったんですよ。つまり、型注釈は基本的にはしない方が推奨されているという印象を、この文から個人的に受けます。
もちろん、型注釈を書く機会があまりないというのは型推論のおかげですね。型推論を生かすと、型注釈をあまり書かなくて済みます。この一文を見る限りでは、型注釈をするよりは型推論を活用していきましょう、という感じが伺えますね。定数や変数の定義義に初期値を提供すると、ほとんどすべての場面で型が推論されると書いてあります。
この「ほとんどすべて」という表現ですが、初期値を提供しても型が推論されない場面というのは何があるでしょうか?あると思いますか?ないと思いますか?
曖昧さがあるので、オーバーロードした関数の戻り値が異なる場合にエラーが出るので、その場合に注釈しないといけないですね。そうですね、素晴らしいです、それがまさにその通りですね。例えば、func calculateValue
がInt
を返すものと、String
を返すものがあった場合です。そして、getValue = calculateValue
としたときに曖昧さが生じます。そのときには型注釈を書かなければいけないです。
「アンビギュアス(曖昧)」という単語、よく見ますね。多分、このケース以外では型注釈はほとんど必要ありませんね。推論できないのは、多分これしかありません。これが派生していって、返す型が例えばString
だった場合、この後にlowercased
などを取れるようにすると、推論が効いて問題なく動くはずです。ただ、曖昧さなく推論できないのはこの場合だけです。
例えば、パラメーターとしてV
を取り、こっちはInt
型を取るよ、とか、こっちはString
を取るよ、というふうにしたときには、パラメーターで推論が効いてきます。そうするとコンパイルが通りますね。コンパイルが通ると、推論が効いてくる。このあたりはAPIデザインガイドラインにも書かれていますね、戻り値の型だけでオーバーロードをするのは型推論に問題があるから推奨しない、ということです。
この勉強会の中でも例えば、Track
のvalue
とか、var
も似たようなもので、Int
型のvalue
を返すものとか、計算型プロパティにしましょう、そのほうが格好が良いし。例えば、var integer: Int
とか、var string: String
みたいなことをして、昔は格好良いから好んで使っていたと話したことがあります。でも、こういったのが推奨されていない理由は、型推論に無理が出てくるからという話なんです。
今日のお話を見ていくと、確かにちょっと使い心地が悪くなってくる感はあります。なので、この「ほとんどすべての場面で型が推論されるけれど、ほとんどから漏れてしまう場面が出てくるから、あまり戻り値だけでオーバーロードしないほうがいいのでしょう」というのが、今日のお話のまとめです。
時間もちょうどよくなってきましたし、スライド的にも良い区切りですので、今日はこれぐらいにしておきましょうかね。はい、1時間お疲れ様でした。ありがとうございました。