https://youtu.be/YPhYV1VHqdM
現在は オプショナル
の基本的機能、とりわけ オプショナルバインディング
について眺めていっています。今回は、この前に話した シャドーイング
についての残りのスライドを見てから、続けてオプショナルバインディング使用のレパートリーを広げていく感じの話題になりそうです。これまでに余談として話してきたことが改めて登場してくる流れになりそうですけれど、再確認しつつ、そこから何か思い浮かぶことがあれば脱線しつつ、おさらいしていきますね。よろしくお願いします。
今回はゆめみ社外の人への一般公募はなかったようなので、基本的に社内メンバーのみでの開催になりそうです。
—————————————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #159
00:00 開始 00:33 オプショナルバインディングの特色 01:03 スライドの間違い修正 03:21 オプショナルバインディングの解説 04:59 シャドーイングの特色 05:41 書籍で丁寧に説明されているところに重要さを窺う 06:23 オプショナルを Any で扱うとき 07:24 文字列補完構文と Any におけるオプショナルの警告 08:32 オプショナルな値を Any に入れることで生じる不都合 09:31 オプショナル型のメタタイプ 11:49 関数の引数における Any とオプショナルの扱い 12:43 Any で扱うときに警告されないようにするには 13:23 文字列補完で扱うときに警告されないようにするには 16:06 インスタンスの文字列化と String(describing:) についての詳細 17:45 ジェネリクスを使って Any 型を受け取るとき 18:15 nil 結合演算子 18:29 型パラメーターで透過的に型を受け取る 23:25 ジェネリクスでオプショナルを想定する 25:34 オーバーロードにおける、型パラメーターと Any との区別 27:34 String(describing:) についての再確認 28:23 ExpressibleByStringInterpolation の観察 30:53 クロージング ——————————————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #159
では始めていきますね。オプショナルバインディングの特色について、この辺りは前回お話しした通りではありますが、これを踏まえてもう少し話が続いている部分もあります。一応こうやって表示はしていますが、次のスライドから解説が続きますので、そこから見ていく感じになると思います。
オプショナルバインディングの特色として、使い方の基本的な雰囲気を掴むためのスライドですね。次に行きますよ。さっきのコードの解説に進む前にまた確認したいことがあります。このプリント文のダブルクォートが変じゃないかと感じます。これをコピペしてきたので、ちょっと確認しましょう。ここはエスケープしていて、閉じてない部分があるので表示が半端になりますね。文法的なエラーではないので動くと思いますが、possibleNumber
が数字に変換できると仮定して、myNumber
に変換します。
var possibleNumber: String? = "123"
if let myNumber = possibleNumber {
print("My number is \\(myNumber)")
}
このコードは possibleNumber
が数字に変換できるかをチェックする例です。このように possibleNumber
が値を含んでいる場合、新たな定数 myNumber
にその値がセットされます。この部分はシャドウイングの例ですね。if
文内で書くと、元はオプショナル型ですが、ここでは普通の定数として myNumber
を参照できます。
if
文の開始前と終了後で myNumber
と書くと、オプショナルの possibleNumber
定数を参照します。この説明について、コードの中で実際に確認するとわかりやすいですね。でも説明文だけでなく、コードも一緒に表示されていたほうが読者には親切かなと思います。
if
ブロックの外で使おうとすると動きはしますが、ここではオプショナル型として扱われます。このスコープの外に出るともはやこのオプショナルバインディングで宣言された定数は破棄されているので、シャドウイングが解けて元のプロパティを参照することになります。シャドウイングが解けるという言い方も適切ではないかもしれませんが、そういう動きになります。
以上がオプショナルバインディングの基本的な仕組みの説明でしたね。この機能は非常に役に立ちますし、これを使うことでコードの安定性が大きく向上します。だからこそ、こういった詳細な説明がなされているのだと思いますね。重要な部分なので、慎重に説明されていて良いと感じます。 気になるところはありますかね。自分はどうだろう、特に気になるところはないですね。大体話しているので問題ないですよね。せいぜいあるとしたらこの辺ですかね。オプショナルの値をプリントしようとすると、適切に処理しないと警告が出るというところですかね。
例えば、print(optionalValue)
とすると、Optional(100)
のような表示になってしまったり、値がnil
だったりすると、nil
と表示されます。何の問題もないように見えるかもしれませんが、ときおり意図しない表示が出てしまい、Twitter上などで騒がれることがあります。こういったオプショナルの扱いには注意が必要です。
特にany
型を受け取るところにオプショナルを渡そうとすると警告が出ます。この話は結構前にも最近にもあった話題です。例えば、print
に限らず、何か変数anyVariable
があったとして、そこにオプショナルの変数を渡すと警告が出るということです。
警告の内容が少し異なりますが、これはストリングインターポレーション、要は文字列補完の専用の警告ですね。うっかりオプショナルを扱うと意図しない表示になってしまうことがあります。推奨側にとってもプログラマーにとってもそうです。具体的な表示を意図しているのに、オプショナルだったことによって予期せぬ表示になる可能性があります。これを防ぐための仕組みです。
また、any
に対してオプショナルを入れる場合、オプショナルの中で扱っている値をany
に入れると、その後何かと問題が起こることがあります。ざっくり言うと、オプショナルを取り出すのが難しくなるのです。以前お話ししましたが、オプショナルバインディングを使っていても、any
の中にint?
を代入してしまうと、nil
だろうと値が取れてしまうことがあります。
any
型のオプショナルに対して具体的に値を入れると、例えばany
のオプショナルにint?
を入れると、大丈夫かどうか検証が必要です。タイプオーバーは完全にint
として出てしまうので違うとしたら、オプショナル型として存在しているためでしょう。any型のオプショナル
という形になっても、結局プリントされた通り、オプショナル型でそのコンテナ内の型としてany
型を取っているだけということです。
これが混乱する部分ですね。ダイナミックにインスタンスの型が取れている場合、意図しないところで困惑するかもしれません。しかし、重要なのはany
型のオプショナルがあくまでオプショナル型であるということです。この点を理解しておくと良いと思います。 とりあえず、any
型はちょっと厄介なところがいっぱいあって、このままany
型にオプショナルを代入すると問題が生じることがあります。例えば、メソッドの引数でany
型を取る場合、本当にオプショナル型として渡したいのか、それとも普通のint
型として渡したいのかによって意味が大きく変わってきます。
プログラマーは、その違いをしっかりと識別して明示的に指定する必要があります。例えば、オプショナル型として渡したい場合には、適切なキャストを行うことが重要です。
let myNumber: Int? = 100
let anyValue: Any = myNumber as Any
このように書くことで、myNumber
はオプショナルのint
型ですが、anyValue
はany
型として扱われることを明示的に記述しています。
また、文字列補間に関しても同様に気をつける必要があります。as Any
を使ったキャストも可能ですが、String(describing:)
を使うことでより明確に挙動を制御できます。
let anyValue: Any = 100
let stringValue = String(describing: anyValue)
こうすると、any
型は標準の文字列変換機能を使って文字列に変換されます。これにより、明示的に文字列に落とし込むことができます。
String(describing:)
を使うことで、さらに明確に「このインスタンスの文字列表現を取得したい」とプログラマーが意図していることをコードに反映できます。Swiftでは、すべてのインスタンスは文字列表現可能という大原則があるので、この方法を使うのがより適切かもしれません。
したがって、Swiftのガイドラインに従うのであれば、as Any
よりもString(describing:)
を使った方が良い場合が多いです。理由としては、as Any
だと意図しない挙動が起こる可能性があり、特にカスタムな文字列補間を行う場合において違った動きを見せる可能性があるためです。
let myCustomValue: SomeType = ...
let stringValue = String(describing: myCustomValue)
このように書くことで、myCustomValue
の文字列表現を取得することができ、意図した動きをコードに明示的に表現することができます。 なので、こうやって見てみると、これがストリングのイニシャライザーとして搭載されていて、インスタンスはオブジェクト型で、サブジェクト型は型パラメーターで定義されています。型パラメーターで定義されている型は何も制約がなければ Any
と同等になります。つまり、これは Any
を取っているということになります。そのため、 Any
型としてマイナンバーを渡せる感じになるわけです。
話していて気付きましたが、12行目は警告になりません。 Any
型を取っているのに面白いですね。ジェネリックスだともしかしてそうなるのでしょうか。ちょっとやってみましょう。ここ、警告になっているでしょ。サムシングのバリューが Any
型で。これが Any
を明記するか、標準アンラップするか、それともデフォルトバリューを提供するか。デフォルトバリューを提供するというのは、ニル結合演算子を使って、ニルだったら 0 にしようかとか、そういったことです。
これを Any
じゃなくて、例えば、型パラメータをバリューにしてジェネリックスでバリューを取るとどうなるでしょうか。警告は消えますね。面白いですね。 Any
型として取っているわけじゃないから、ということか。ああ、そうか。自分の中でパラメータとして取る Any
と、ジェネリックパラメータとして取る Any
は同じだと思っていましたが、違うんですね。よく考えれば、確かに違います。
どうすると違いが分かるでしょうか。プリントしてみましょう。 print(type(of: value))
としてみればいいですね。これで実行すると、オプショナルの Int
が取れるでしょう。ここが重要です。多分、 Any
に変えてそれをプリントすれば良いのでしょうが、情報は作れないかな。ちょっとやってみましょうか。オーバーロードだとできなかったですね。
とりあえず名前を変えて、1 と 2 とかにしましょう。1 のほうがジェネリックパラメータ、2 のほうが普通の関数で Any
を取るようにして、サムシング1 と 2 の2つを実行してみます。あ、一緒か。間違えているのかもしれませんね。ダイナミックに型を取ってしまうからかな。この差を明確に出すためには、タイプオブじゃなくて、バリュー型のセルフにして、こっちを Any
型のセルフにして違いを見ればいいのかな。
こういう感じで、ジェネリックパラメータは実際のプログラムを動かす段階ではなく、コンパイルタイムに実際に渡ってきた型に応じてパラメータの型が決定するという動きの特徴があります。そのため、実質的に Any
であったとしても、実際に動かす時点では、例えばオプショナルの Int
を受け取っているというのと同じ動きを見せます。そのため、オプショナルの Int
のメタタイプが表示されます。一方、 Any
の方は本当に Any
を受け取るので、 Any
のメタタイプが得られます。
確かに、ジェネリックパラメータを使ったときと全然違いますね。改めて見ると驚くこともあります。
さて、ジェネリック関数についてですが、これが Any
ではないということは、オプショナルバインディングが使えないということです。 if let value = value
という形にすると、右辺がオプショナルではないと言われます。これを Any
として受け取る場合には、オプショナルのケースも考慮しなければならないですね。
型 T
をバリューとして、バリューのオプショナルとして取る場合には、このようにします。オーバーロードするとどうなるでしょうか。コンパイルが通るか試してみます。
いずれにしても、オプショナルの場合も考慮する必要があります。また、通常の Any
型とジェネリック型での動作が異なることを理解することが大事です。 とりあえずここまで戻して、オプショナルバインディングも使えないというところまで話を戻しましょう。このようにジェネリックパラメーターを使用すると、Any
を渡さなくても良いですね。ヒントとしてオプショナルを渡しているから警告が出ないのは当たり前すぎますね。その通りです。もし Any
として渡したい場合でも、特に問題はありません。
ちなみに、私が勘違いしていたジェネリックパラメーターの Any
と実際の Any
が同じだという話は間違っていました。オーバーロードしてみると、警告が出るかもしれませんが、特に何も言われませんね。この件は、後で勉強会のどこかで詳しく話したいと思います。
また、ストリングディスクライビングの話もOKですね。まず、カスタムストリングコンバーティブルが実装されているかどうかによって、あればそれを使って文字列変換しますし、それがなければカスタムストリングコンバーティブルで変換します。それもない場合、最後は言語組み込みの文字列変換方法が使われます。
ストリングインターポレーションに準拠した型を特定に作ると、結果が少し変わるかもしれません。ストリングインターポレーションはデフォルトでは DefaultStringInterpolation
に色々と実装してあるため、それに準拠させることでさまざまな動きをさせることができます。例えば appendLiteral
とかですね。
ただし、今回の勉強会では時間がないので、個人的に後で試してみます。今日はここまでですが、次回はオプショナルバインディングや文字列保管、Any
の話の続きから始めましょう。
では、今日はこれで勉強会を終わりにします。お疲れ様でした。ありがとうございました。