本日も引き続き 強参照循環
を解消する手段のひとつの無所有参照
を オプショナル
で扱うことについてを眺めていきます。前回に見た概要を踏まえて、実際にどのような様子になっているかを見ていく回になりそうです。よろしくお願いしますね。
———————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #241
00:00 開始 00:24 オプショナル型と無所有参照は分けて考えれば良い 01:06 前回のコード例のおさらい 01:42 所有するという言葉の違和感 03:03 オプショナル型であることの理由 03:49 序列があるものの扱いについて 05:54 参照の様子を見てみる 08:02 思いのほか複雑な印象 09:14 受講順とは別の、一覧表としての序列 10:10 Set 型はよく使う? 12:33 Set 専用のリテラルがない 13:23 Set ではなく Array を求められることが多い 13:34 無作為に並べるために Set を使う? 14:46 ストロベリーとイチゴ 16:46 Set でシャッフルされるとは限らない 19:50 選択肢の持たせ方のアイデア 22:54 ジェネリクスの普及で Set の様子も違ってくる? 28:05 クロージング ————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #241
では始めていきますね。今日は「無所有なオプショナルの参照」についてのお話です。ちょっと難しそうに聞こえるかもしれませんが、基本的には「アンオウンド」をオプショナルで使うという話です。前回話した内容からだいたい結論が出たと思いますが、オプショナルで扱うという話は「アンオウンド」とオプショナルという二つの概念を分けて考えると理解しやすくなります。そのため、実際には普通に使えそうだねという結論になりました。
さて、もう少し話が続きますので、前回の内容をおさらいしつつ、新しい内容に進んでいきます。多分、このコードの解説を読んだのかもしれませんが、ここでは学科の「デパートメント」があって、そのデパートメントがコースを所有しています。それぞれのコースが何の学科に属しているのか、次にどのコースを受けるのかという構成になっています。
まず、学科がコースを所有しているということですが、コース自体はどのオブジェクトも所有しないという表現があります。これは、共産党のコースを持たないという意味ではなく、所有権がないという意味です。この点を理解するのが少し難しいかもしれませんが、所有していないという表現で通じるかどうかを考えながら進めます。
次に、全てのコースは何らかの学科に属しているので、これはオプショナルではないです。ただし、あるコースには続くコースがない場合もあるので、その場合はオプショナルになります。ここで重要なのは、アンオウンドなオプショナルであるものの、続くコースがないこともあるためオプショナルが使われるという点です。この考え方を押さえておけば、無所有なオプショナル参照という表現に対して身構えることはないでしょう。
さて、実際のコード例に入ります。例えば、インスタンスを作成して、イントロ
の次のコースはインターメディエイト
、インターメディエイト
の次のコースはアドバンス
、という風になっています。アドバンス
の次のコースは存在しないためnil
です。ここで、推奨コースというのは次に受講するコースのことを指しています。
デパートメントの構築において、インターメディエイト
、アドバンス
というコースの順番が問われない仕様になっています。ループでコースを取得する際に、その順番が実際にどうなっているかは順序を保証しないので注意が必要です。
一般的にコードを書く場合、あまり順番に気を配らないことが多いかもしれませんが、特に序列が重要な場合には注意が必要です。ここでの注意点は、バグが重大なものを引き起こさない可能性があるときには、これで問題ないということです。
次に、この構造がどうなっているのかを見ていきます。デパートメントがあり、そのデパートメントがコースの配列を持っています。この配列から、1個目、2個目、3個目という風に参照が行われます。この配列は内部に共産省で持つ性質があるため、共産省になっています。
それぞれのコースには、次のコース(next course
)を示すプロパティがあり、インターメディエイト
がアドバンス
を、アドバンス
がnil
を示すようになっています。また、コースがデパートメントにどのように所属するのかも示されており、これらがアンオーンドになっています。
この構造を見ていると、オブジェクト間の参照が一方向のみで共産省の循環が起こらないため、デザインとしてはうまく設計されています。ただし、こういった複雑な管理は意外と難しいこともあり、注意が必要です。特に、さらに複雑な構造になる場合には、適切な設計が必要です。
以上が今回の内容です。少し複雑で難しいかもしれませんが、このように順序やオプショナル、アンオーンドの参照について理解しておくことが大切です。 アイデンツここのコースに行くと、実際のところがここがコースの一覧みたいに捉えると分かりやすいですね。確かにそうです。で、一覧表ではなく順番もあるしね。別に次のコースと全然関係ないってことですね。確かにそうやって考えれば、最初自分が話していた「色んなところに序列があるのが気持ち悪い」っていうのが、すっきり収まりそうですね。
この時、どうなんだろう。例えばセットで扱うってどれくらいの人がしますかね。序列が関係ないからここはセットだよねって感じで。あまりしないかもしれないですね。あえて序列がない時にはセットを持ち出したり、あとは重複しては困る時とかね。そういった時にセットにする。もちろんキーがハッシュ可能じゃないといけないっていう条件がつきますけど、配列の場合ってハッシュ可能でも成り立つじゃないですか。
で、ハッシュ可能は置いておいて、セットでも成り立つ。セットにすれば序列がなくなる。なくなると、要素をランダムにしか並べられなくなるからとか、そこで必要が出てきますよね、プログラム組んでると。勢いで。
でも序列が必要になった時に、「IOAO順で並べる」とか「人気のコース順に並べる」とか、その時に味付けすれば済むっていう発想ももちろんあるので、そこまで考えていくとセットもありえますよね。セットってよく使いますか?
自分の場合、ハッシュタグを重複させたくないツールを作っていて、このツールでツイートする時にセットを使いました。それはハッシュタグの順番にこだわらなくてもいいなと思ったのが一つの理由です。あとは全体的に考えて配列にする必要もなかったので、セットの方が良いと思いました。せっかく専用のリテラルがないっていうのも結構大きいかなと思ったりします。
セットを使う場合、デフォルトで配列になっちゃうから、わざわざセットって書くのが面倒ですけど、規定の型になるっていうほうが扱いやすいんですよね。Int
と同じで、いろんなサイズのInt
があるけど、普通はInt
型にしますよね。そうすると円滑にコードが書けるってことですね。なるほど、配列を求められること多いですよね、それも支障があるポイントの一つですね。
面白いな。コメントいただいたフィードバックの使い方で、選択肢の順番を変えるためにセットを使ったって、面白い使い方ですよね。でも、セットも内部のハッシュ値がランダムになってるおかげで、ランタイムで順番が変わることもある。それを踏まえたうえで面白い発想としてとても良いですね。やってみると良いかも。
例えば、
let values = ["Apple", "Melon", "Grape", "Orange", "Strawberry"]
全然余談ですが、「Strawberry」と「イチゴ」って聞いたときの印象が違いますよね。ストロベリーって聞くとピンク色のクリームっぽいのを思い浮かべます。イチゴって聞くと粒々の種がついた物体を思い浮かべる。不思議なんですよね。 言葉と意味は、その印象から関連付くのではないでしょうか。例えば、自分の中で「牛」と「馬」と「豚」という言葉を聞くと、すぐに動物が思い浮かびます。しかし「牛肉」「豚肉」「馬肉」と聞くと、一瞬ずれる感じがします。これは、その動物たちが具体的な食材として認識されるためかもしれません。このように、普段は意識していないときに何かの言葉を聞くと、少し混乱することがあります。
たとえば「サーモン」と「ジャケット」などの言葉の違いや、「ストロベリー」を見ると印象が異なることがあります。これは言葉が持つ意味がどのように関連付けられているかによるものです。例えば、プログラミングの例として、printValues
という関数があり、それを実行すると、リストの要素を表示できます。仮に変更を加えると順番が変わることがあります。
たとえば、リストに要素を追加する場合、append
メソッドを使って、新しい要素を追加できます。ただし、セットに対しては直接使えないので、別の方法が必要です。例えば、insert
メソッドを使うと、要素を特定の位置に追加できます。このように操作によって順番が変わることがあります。
ハッシュテーブルに基づいたデータ構造では、要素の追加や削除が順番に影響を与えることがあります。例えば、remove
メソッドを使って要素を削除するときにも、ハッシュテーブルの再構築によって順序が変わることがあります。クイズ番組の問題や選択肢を管理する際にも、ランダムな並び替えや正解の配置が求められます。
クイズの問題と答えを用意するときに、正解と誤答をどう管理するかが重要です。例えば、questions
とanswers
のリストを用意し、最初の要素を正解、それ以降を誤答とすることで、問題のシャッフル表示が簡単になります。表示するときにはリストをシャッフルし、正解がどのインデックスにあるかを確認して表示する方法が考えられます。
このような方法を使うことで、プログラムの問題の管理が効率化されるでしょう。これを思い出したので、共有してみましたが、他にも意義ある方法があるかもしれません。 とりあえず、これはこれで問題点があるとすると、インデックスゼロが正解だというのはローカルルールでしかないので、分からない人が見ると理解できません。そのため、正解はこれで残りは間違いの選択肢といった形で、ちゃんと型で説明したほうが良い可能性があると思います。たとえば、型やラベルを使って説明するのが良いでしょう。
もちろん、そちらのほうが良いのかなという気もしますが、この方法も面白いと思います。ただ、これを実際に行う場合、セットとしては無理があります。個人的な意見ですが、興味深いです。セットにすることで順番が変わることを考慮していなかったので、それを活用するアイディアは面白いと思います。
具体的な例として、セットを何気なく使用すると問題になるケースをご紹介します。例えば、誰かがAPIを作るときに、values.map { $0.count }
のように使うとします。これがセットではなく配列になります。配列なら問題ありませんが、あえてセットにする人は少ない気がします。セットにわざわざするのは面倒ですから。
多くの人は、結果を出すときに配列にしますよね。例えば、let values = [1, 2, 3]
のように。これで values.reduce(0, +)
のように合計を求めることが多いです。もしこれをセットにした場合、非常に面倒なコードになってしまいます。そういった点を考慮すると、セットは特殊な場面でしか使えないという印象になりがちです。
ただ、このあたりはジェネリクスをうまく使えば回避できることもあります。例えば、some Collection
や Sequence
で扱うことが出来ます。これによって、すべてを一度に解決できます。API設計としては、そろそろ次の時代では some Sequence
で対応するのが良いかもしれません。
具体的には、ある数値の配列を合計する関数で、T: BinaryInteger
としてジェネリクスで対応する方法があります。これによって、どんな数値の配列でも合計を求めることができます。例えば、いくつかの異なる整数型(Int8
や Int16
など)でも対応可能なのです。このようにすることで、使いやすいコードになるかもしれません。
ただし、パフォーマンスの問題があるかもしれないため、実際に使う場合は注意が必要です。しかし、ジェネリクスも最近ではかなり使いやすくなってきていますので、積極的に使っていくと良いでしょう。そうすることで、セットの役割や使い方も変わってくるかもしれません。
以上、時間になりましたので今日はこれくらいにしておきましょう。