https://youtu.be/jmtNOgimlFM
今回も引き続き The Basics
のタプル
について眺めていきます。前回はタプルの特徴的なところを見ていったので、今回はそれを操作する上での機能まわりの特徴を意識しておさらいしていこうと思っています。よろしくお願いしますね。
———————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #131
00:00 開始 00:47 タプルの概要(おさらい) 02:24 引数リストとタプル 03:27 タプルスプラット 07:44 カリー化について 15:00 UnsafeRawPointer.bindMemory 15:44 UnsafeMutableRawPointer.storeBytes 17:46 タプル全般を扱うものにはならなかった様子 18:36 Mirror ではキーパスを取得できない 20:29 タプルの分解とパターンマッチング 21:48 タプルの活用例 24:27 URL の既定イニシャライザー 25:30 複数要素の一部を取り出したいとき 25:47 代入式とパターンマッチング 27:13 タプルの分解と可読性 29:01 状況に応じてマッチングするかが変わるパターンは代入式で使えない 32:29 クロージング ————————————————————————————————————————
Transcription: 熊谷さんのやさしい Swift 勉強会 #131
はい、じゃあ始めていきますね。今日もタプルのお話を引き続きしていきます。前回はタプルの基本的な型としての特徴を見ていったので、今日は応用と言っても大げさなものではなく、インスタンス化したタプルの使い方を見ていきます。前回と少し重複する内容もありますが、さらっと確認しつつ進めていきましょう。
念のためにおさらいしておくと、タプルとは複数の値を一つの複合的な値にまとめるものです。また、型の定義としての視点を変えると、複数の型を一つの複合的な型にまとめるとも言えます。この言葉にはちょっと違和感があるなという話を以前しましたが、一応そういったものです。
構造体と非常によく似ていますが、構造体はノミナルタイプでタプルはノンノミナルタイプです。ノミナルタイプとノンノミナルタイプという分類があって、それによって型拡張ができるかできないかなどの違いが生まれてきます。タプルはノンノミナルタイプで軽量で手軽に複数の型をまとめられる便利な面があります。
まだあまりタプルを使いこなせていない自分には、あまりなじみがない感がありますが、タプルの機能として便利な部分はたくさんあります。ただ、自分の中では微妙な位置にあります。Swiftでは大きな特徴として打ち出してはいないですが、関数のパラメーターとしてタプルが使われています。内部的にはタプルとして存在しているのではないでしょうか。例えば、Int型とString型のタプルをパラメーターとして受け取るというようなイメージです。ただ、Swiftが出て間もない頃はタプルとして打ち出していましたが、途中からコンパイラーの負荷などの都合で、パラメーターをタプルとして表だって扱わなくなったという感じですね。
タプルスプラットについても話をしました。この勉強会で以前に何度も出たような気がしますが、例えば、値を複数持つタプルを関数の引数として渡す方法などがありました。ジェネリクスを使うとパラメーターとしてタプルを受け取ることができます。
具体的な例を挙げると、関数のパラメーターとしてタプルを受け取ることができます。例えば、関数action
に対してタプルvalues
を渡したいとします。通常は直接タプルを渡すことはできませんが、プロキシ関数を作って間接的に渡すことができます。以下のようにすることで、タプルを経由して関数を呼び出すことができ、コンパイルも通ります。
たとえば、戻り値がAny
型のaction
関数があり、そのプロキシ関数がタプルを受け取ってaction
を呼び出すようになっています。こうすることで、関数呼び出しにおけるパラメーターリストがタプル型であることを証明できます。
また、関数を呼び出すときに、パラメーターのリストをタプル型として定義し、そのタプル型をスタックに記録し、関数の呼び出しが終わったときにタプルを解放するといった処理が行われます。タプルにまとめるところがネックになることもありますが、このようにして使うことができます。 それでは、少しやってみましょうか。カリー化というのは、私はそんなに詳しくないですけど、このアクションで、VとWみたいな二つのパラメーターがあったとき、例えばどちらか一方、Vだけパラメーターを決めて、パラメーター一つだけの関数に変える、みたいな感じです。昔はそういう風にできたんですよね、それがカリー化です。どうなんでしょうね、無理やりやったら面白いですけどね。
ここはラベルを変えたからだと思います。さっきラベルを変えたばかりなのに動いてなかったですよね。時々プレイグラウンドではそういうことがありますよね。まあ、良いでしょう。パンクカリーとして、これは難しそうですね。
タプルクロージャーを使って関数を作ればできるから、要は汎用的なカリー化ですね。難しそうです。値を1個混ぜ込んでタプルに値を追加するのは可能でしょうか。メモリサイズを考えないといけませんね。ちょっとやってみましょう。時間がないかもしれないけれど、例えばlet x
として整数型を持っていて、y
としてタプル(数字と文字列のタプル)があって、これをイントとストリングとイントのタプルに変えたいという感じです。
そうするとメモリを確保しないといけません。そのときにメモリーレイアウトでタプル型のサイズを使って、アンセーフミュータブルバッファーポインターでやってみましょうか。まずロケーターがありますね。これでサイズ分取ります。エラーが出ているのはジェネリクスの問題です。ここで整数型にすればいいのかな?これでメモリが取れて…。トラクトじゃだめですね。どうやるんでしたっけ。バッファーにロードする方法が合ったような気がするけど、違いますね。メモリに書き出す機能があるんですが、バインドか?違います、忘れてしまいました。
バッファーを使わずにアンセーフローポインターにして、バッファーのベースアドレスを変換できるでしょうか。ちょっとここでコメントアウトしてみます。とりあえずここまではできました。ポインターにバインドメモリーする必要がありますね。これでタイプがタプルでキャパシティがサイズです。アンセーフポインターでいいでしょう。そうするとセルフを忘れました。ここまで動きます。でもZがアンセーフポインターになっているのは何でしょう。ポインターを返していますね。キャパシティが1でいいのか。こうするとバッドアクセスが発生します。メモリが足りないのか、確保したメモリの容量が足りないのが原因かもしれません。
この複雑な操作をする必要はないかもしれません。最初にメモリをバッファーポインターで作ってしまうべきかもしれません。やはりアンセーフミュータブルポインターとして、タプル型としてアロケートキャパシティ1でいいです。これでPに代入していきます。メモリレイアウトのタプルのオフセットが必要です。インスタンス化しないとならないので、メモリレイアウトのオフセットオブ、キーパスが必要ですね。これでキーパスとして0番目を取得します。これでちゃんと値が取れますね。
次に、アンセーフミュータブルポインターをアンセーフローポインターに変換します。これで書き込みを行います。適切な方法でメモリに書き出す必要があります。ストアバイト、アンセーフミュータブルローポインター、そうです、ミュータブルじゃないと使えないですからね。ここまで変換できます。アンセーフミュータブルポインターに変換し、キャパシティ1でアロケートします。次に、個々のオフセットに対してインスタンス化してメモリを書き込みます。 なので、今回は決め打ちしちゃいますかね。ここにYの0番目を決め打ちじゃなく、ちゃんと変数から取って。これで次がストリングのセルフでYの1番目でしょ。これでストアバイトの、ここで自分でXを渡して。それで、あれ?オフセットがなんか偉いことになってるね。型が違うのかな?あー、オプショナルになってる。この辺ちゃんと取れるからオッケーでしょ。こうやってあげるとタプルが作れたんじゃない?これで。で、プリント。Qのなんだっけ?ポインティか。違うね。Qがアンセーフローポインターだから、もうPに入ってんだ。これのポインティを取ると、もうできたじゃないですか。タプル作ったって感じ。あ、こんな難しいことしなくていいのか。最初からタプルを定義して3番目に入れればいいのか。
これを汎用的にやろうとすると、要はメモリをダイナミックに確保しようとすると。あ、でもここがいるね、ダメか。これだけでもここを動的にやんないといけない。あ、でもそんなことないね。だから最初からアンセーフミュータブルローポインターを作って、アンセーフミュータブルローポインターってアロケートできるよね。バイトカウントでバイトサイズのメモリ確保できるからこれで確保して。あ、でもその後このメモリレイアウト取んないといけないのか。
だからまあ、取り方としてはね、このパーシャルキーパスさえ渡せればいいんで、パーシャルキーパスを渡さないといけないのか。ミラー、ミラー型って確かないですよね、あのキーパスはね。ミラー型でキーパス取れればいいのにね。うーんと、ミラー型ってチルドレンは取れるけど、チルドレンが確かね、持ち列で取れちゃうんですよね、ミラーチャイルド型。で、ラベルがストリングね。ここがキーパスだったらすごい嬉しいですよね。いろいろ応用が効くと思うんですけど、取れなくなっているのが残念だな。
ここでもしね、ミラーでキーパス一覧が全部取れちゃったとしたら、それによって、まあ順番がどうなっているかわかんないけど、多分上から順番に得られると思うんで。そしたらオフセットをね、メモリレイアウトで取れるんで、メモリレイアウトでオフセットさえ取れればそこにダイナミックに書いていけるよっていう感じになって、タプルのサイズがいくつであろうとしっかりとね、これプラス1個みたいな、そういったことができるようになってくるかもしれないんですけど、今のところ手がないか。
そこまで手がないとなると、普通にZとしてね、イント、ストリング、イントとしてね、あらかじめ定義して、これでね、ZとしてY0、YX。これやったのと何にも変わらなかったですね。今ややこしいことやったけど、まあいいや。雑談はこれぐらいにして、とりあえず次へ行きますか。
ここも前回話したやつだね。だからここからですね、タプルっていうのは分解ができるよっていうところをちょっと紹介しておきましょうか。これがね、たびたびタプルの話になるとね、便利だよって教えてくれるところなんですけど、なんか自分はついつい使わないんですよね。これと似たような表現としてね、パターンマッチングのタプルパターンっていうのがあって、それはまあ使うんですけど。でもそれと同じなのかな、ワイルドカードパターン使ってますしね、ここね。
で、スイッチ文のね、パターンマッチングの機能もなんか代入文でも使えるよみたいに書いてあるんですよ、確か。なのでね、代入文でパターンマッチングを使ってる、要はバリューバインディングパターンですよね。で、アイデンティファイヤーパターンかなこれ、ちょっと自信ないですけど。で、あとワイルドカードパターン。で、ここでタプルパターンを使ってるみたいな風に捉えてあげれば、自分の感覚的には馴染みやすいのかな。
まあ要はね、分解して代入できる、取り出せるよかっていうね、そういったお話なんですけどね。だから例えばリクエストみたいなメソッドがあって、せっかくだからURLぐらい取っときますか、例ではどうでもいいんですけど。ここにね、URLをリクエストして、で、これでね、イント型とストリング型の結果を得る。まあ一応ラベル付けときますかね、コードと、あとはデスクリプションかな、っていう風に付けてあげてリクエスト。するコードを書いて、それで得られた結果として、例えば200OKみたいなのが返ってくるよっていうときにタプルを活用して、まずここで活用していて、戻り値のマルチプルリターンタイプだったかな、スウィフトの売り文句のうちの一つとして挙げられてるやつね、これを使って戻り値を複数返すことができるよっていうね、そういうやつ。
で、書くときに、これで例えばステータスコードだけ欲しいんだよみたいなときにこうやってね、リクエストに対してURLを渡してあげるとこれで得られるんだけど、URLがデフォルトイニシャライザー持ってないのか。なんでもいいや、カレントディレクトリーでも渡しちゃおう。こうすると、プリントコードってやると200が取れてる。もちろんもう何もないか。ここにもう一個書いてあげると、プリントデスクリプションみたいに書けて、図りが違うかな。違うみたいね。こうやって変数バラバラに取れるよっていうやつ。
変数をバラバラに取るっていうのを知らないと、ステータスイコールリクエストURLみたいなふうに一つの変数で受けて、それで実際に値を使うときにはステータスのコードとステータスのデスクリプションみたいなふうにしないといけない、は言い過ぎかな?こういうふうに書くことになるという感じで、9行目と8行目、9行目みたいなことがAppleの性質を使ってできますよっていうお話ですけど、自分はついつい8行目使っちゃうんですよね。
あ、なるほど。NSURLがデフォルトイニシャライザー、確かに持ってますね。なるほどね、どうでもいい例のときとかには確かにちょうどいいかも。NSURLこれだけで暗黙ブリッジすれば最高なんですけど。
それは何だこのエラーは?ここですよね、やっぱね。なるほどね、あれ?ここもエラー?何か消しちゃったか、消しちゃったね。巻き込んじゃったんだ。とね、だからNSURL。これであとURLが必要。そうね、余計なこと考えないで、とにかく何かURL渡したいよっていうときにはこっちのほうが慣れればかな、いい感じがしますね。
あと、その前にいただいたコメントを見てみると、要素が3つ以上で2つだけ取り出したいときとかに便利かも。なるほどね、エラーも返すようなパターンか。フォールバックとかになってくるとよくあるパターンですよね。 こんな感じでね、これどうなんだろうね。例えばコードが404だったときにエラーインスタンスが添えられるような仕様だったと仮定して、そうしたときにオプショナルパターンだったとしたら、代入式のところがもうちょっと高度なことできたりするのかなとか喋ってたけど、できない気がしてきた。
スイッチ文に使える、要は条件式に使えるパターンマッチングと代入式で使えるパターンマッチングは違うんですよね。違うというか、代入式で使えるパターンマッチングには制約があって、条件分岐ができないので、必ず成立するパターンマッチングしか使えなくなるっていう、そういった特徴があるので。
バリューバインディング、なんだっけ、アイデンティファイアパターンか。それが使えないっていうところはありますね。なので、とりあえずここがエラー型としてこれで、例えばコードとエラー状態だけ欲しいよみたいなとき、なるほど、こういう感じか。こういったときに実行してディスクリプション取ってないから、これは使えないけど、こうやってエラーとして何かエラーが取れてるけど、そうね、確かにそうだ。
もちろん、スペースと取った上で三つが使える状態のまま、ここでエラーなりコードなりディスクリプションなり、こういうふうに取るっていうこともできるんで、どっちがいいかはお好みなんでしょうけど、最初からこうやって11行目みたいに書いてあげれば、この行見るだけでこのメソッドで期待している戻り値はコードとエラー、それだけあれば十分、他は捨てるよみたいな、そういったことが明記されるんで、これはこれで確かに分かりやすいかもしれないですね。
複数ある戻り値の中からいるものだけを残すんだよみたいな感じ。ただし、ここでなんとなく感じる欠点までいくか分かんないですけど、状況によってはグループ化が溶けちゃうんで、コードとエラーが関連づいたものであるかっていう情報は失われるから、関数のボディ部分が長くなっていくようなコードを書くようなとき、そもそもそういった関数が長くなるのって今どきはNGとされがちな気はするんで、そんなに心配ないとは思うんですけど、長くなってきて見返したときにこのエラーとコードが全然関係ないものとして捉えられちゃうと、思考の妨げになるかなっていう不安は感じるような気もしなくもないけど、でもすっきりしてていいですね、11行目ね。
自分は何にも考えずにこういった形で書いてたところがあったんですけど、なるほど、これも選択肢に入れてみるとちょっといいかもしれないですね。ちなみに書いてみれば当たり前なんですけど、200じゃないや404だったときにはエラーを取りたいよみたいなときにこういう書き方はできないですね。
どういうエラーになるのかな、これは。なんだこれ、int、string?なんかエラーメッセージ、微妙ですねこれね。エレメント数が違う。どのエレメント無視したんだ、これは?int、string、エラーがなくなってるね。ちょっと解釈がわからない。これエラーメッセージの判定バグじゃないかな。まあいいや、ちょっと安易なことも言えないけど、なんとなく変ね。
とりあえずこういう書き方はできない、当たり前ですよね。404だったときにエラーが仮に取れたとして、じゃあ404じゃなかったときどうすんのかっていうことがどうにもならなくなってるんでね、このコードだと。これがif文だとできるんじゃないかな。if case let、同じパターンマッチングね。
elseじゃないや、ここがこうね。これでコンパイル通ってここがエラーのところがいいよね。エラーが使われてないになってるだけだから、これでいいんだ。で、else、print、こうやってやるとsuccessになるかな。どこがany?これオプショナルだからか。じゃあローカライズディスクリプションまたは空文字にしますかね、なんでもいいですけどね。ここだ、でこうして、こうだ。こうすると…まだ、どこだ。違うとこか。この辺か、その一番下の方か。また余計なの消したか。いいや、こうしてあげるとsuccessでしょ。で、コンパイル通ってますよね。で、ここで404にするとエラーですよね。うんうん、そうね、ちゃんと動いた。
まあ、こういうふうにね、条件式の中ではパターンマッチングがもうちょっと高度に使えて状況に応じて答えが変化するパターンマッチングが使える。確かに8つのパターンマッチングがあって、そのうちの必ずマッチするパターンが4つと、状況に応じてマッチするかが変わるパターンが4つ。合計8個があるよっていうお話をこの勉強会のパターンマッチングのところでしたと思うんですけど、それを思い出すと、このif文の時のパターンマッチングで使えるもの、で、こうやって代入文の時に変えるものとかね、ちょっと見えてきそうね。
まあ、とりあえずちょっとタプルパターンの話に移っちゃったけど、まあタプルのね、分解という面では同じかな。こういったね、タプルを分解して代入したり、条件式で上手い具合に捌いていったり。で、さっきねifケース文書きましたけど、スイッチ文でも同じ。スイッチ文でステータスが200だった時にはメッセージを取って、404だった時にはエラーを取るみたいなのもスイッチ文で簡単に書けるっていう感じね。
はい、じゃあまあこんな感じでいい具合の時間になったんで、タプルについてのお話はもうちょっとしたのかな。なので次回もするような気がしますけど、とりあえず今日はこれぐらいにしておきましょう。お疲れ様でした。ありがとうございました。