今回もお気に入りな技術ブログ「その Swift コード、こう書き換えてみないか」を眺めていきますけれど、その中から引き続き
名前空間
と enum
まわりについてを眺めていきます。前回では時間の都合で触れられなかった 菅原さんのブログ を見たりしながら、名前空間としての enum
利用についてみんなで想いを馳せていけたらいいなと思っています。よろしくお願いしますね。————————————————————————————————————
熊谷さんのやさしい Swift 勉強会 #265
00:00 開始
00:27 今回の展望
01:04 名前空間と列挙型
02:21 名前空間専用のキーワードはない
03:46 列挙子を持たない列挙型の活用
04:49 コンパイラーからは使用を許されている
05:47 列挙子を持たない列挙型の利用場面
06:32 MemoryLayout も列挙子を持たない列挙型
10:26 MemoryLayout 型の定義
11:05 ほかにケースレスな列挙型は使われている?
14:00 SwiftLint でも検出可能らしい
14:57 ケースレスな列挙型を使って嬉しいこと
16:05 MemoryLayout がインスタンスを生成させるとすると
18:48 オブジェクト指向的か、構造化的かの違い
19:58 インスタンス化できないことを明瞭に示す
22:29 それなら名前空間専用のキーワードがあってもいい?
23:57 名前空間の作成は推奨されていない?
25:27 名前空間は慎重に使っていくのが良いかもしれない
26:49 今回の所感と次回の展望
————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #265
はい、では始めていきましょう。今日も引き続き、このブログのお気に入りのSwiftコードを取り上げて書き換えてみようという提案の話題についてです。これは、楽しくて永遠に読んでいられるブログですね。
前回から続いているこのテーマは、今回も非常に興味深い部分、特に「名前空間に列挙型を使おう」という話題が出てきました。前回、ブログの内容を教えてもらった後でさらに深掘りしようと思ったのですが、時間の都合で見れなかったので、今日はその部分を簡単に振り返りながら進めていきたいと思います。
Swiftには名前空間があります。これはSwift 1が登場したときから大々的にアピールされていました。名前空間が型として関連付けられているため、いろいろな活用方法があります。型と関連付けられることで、型をしっかりと使い、その中に所属させるという意味での名前空間がSwiftの重要な考え方です。しかし、列挙型を名前空間として使うと、それが緩くなり、使い方を誤ると問題になることがあります。
だんだんと経験を重ねるうちに、「これはアリかな」とも思うようになりましたが、乱用は避けるべきだと思います。名前空間のキーワードがもし存在していたら、おそらく多くの人が乱用していたでしょう。これは他のプログラミング言語でも起こりうることですが、標準ライブラリにすべてが入っているような状況が発生しないようにするための工夫です。
Swiftでは型があることによって、一歩考える場面が増え、型定義の重みが考慮されるため、いい選択だと思います。それでも列挙型を名前空間として使うことは慎重に考えるべきです。
前回この話題に関連して面白いブログ記事を教えてもらいましたので、今日はそれを読んでいきたいと思います。そこまでボリュームは多くないようです。まず、クラスレス列挙型(正しくはケースレス列挙型)という言葉が出てきますね。一般的に、列挙型にはケースが存在しますが、ケースを持たない列挙型という新しい概念が提案されています。
この発想はなかなか出てこないもので、自分もこのアイデアには驚きました。しかしSwiftではケースレス列挙型がコンパイルを通るので、これはコンパイル設計者が意図的に許しているものでしょう。「使って良い」と解釈していいのかもしれません。ただ、状況次第では議論の余地もありますが、少なくとも現状では「使って良い」とされています。
ケースレス列挙型が使える状況として、クラスや構造体が静的メンバや静的関数のみを持つ場合などがあります。この機能を応用して、ケースを持たなくても有意義な列挙型を書くことができるというわけです。 とりあえず、標準フレームワークのメモリーレイアウトでも採用されているということで、これはなかなか面白いなと思うと同時に、メモリーレイアウトという表現が妙だなと思っていました。でも、もしかするとこれはケースレスに関係して妙に見えるのかもしれません。
もともとメモリーレイアウトという概念は途中から導入されたもので、最初は
sizeof
という関数がありました。C言語やObjective-Cだと有名ですよね。たとえば、let x = sizeof(x)
のような書き方は昔よく使われていたんですが、この sizeof
関数がなくなったんです。今だとエラーになってしまいますね。昔はエラーでもリプレイスする手段があった気がしますが、正確には忘れました。とにかく、メモリーレイアウトという表現と
sizeof(x)
というのは確かに妙に感じるかもしれません。関数として書かれているのではなく、メモリーレイアウトのサイズを示すこの書き方には微妙な違和感があります。どこに違和感を感じるのか考えてみたんですが、関数ではないからかもしれません。名前空間に所属しているかどうかだけの違いであり、むしろ逆に何のサイズか分からないことがなく、メモリーレイアウト的な価値観でサイズを取るという感じに読めるので、悪くはないですね。確かに、メモリーレイアウト以外に何があるのかということも探せますし、立派な概念立てとして受け入れることができそうです。あと、立頂型の世界観さえ崩れなければ、大賛成になるのかもしれません。
こうやってメモリーレイアウトを使用することができます。初期化していないとエラーが出るので、その点には注意が必要です。コンパイラにどう表示されるのか、例えば正規表現でINAMスペースを探すと、意外と見つけにくいかもしれません。INAM後に続くプロトコルを含めたり、あるいは他の要素が続く可能性があるため、正規表現は長くなりがちです。
このように、メモリーレイアウトの取り扱いや考え方自体に似たような斬新なアイデアが見つかることもあるでしょう。これをしっかりと理解していくことが、Swift言語の深い理解につながるのではないかと思います。 NSを使い始めると、正規表現の作業に取り掛かることになります。この後、何か文字列操作をしたい場面で、会議音が多数マッチするかもしれません。たとえば、会議音が0個以上マッチする、その後にケースが続く、または空白文字が1個以上続くケースをマッチさせる必要があるとします。
この場合、もしケースが存在しない場合はどうするかですが、例えば「?」などの文字を使ってもXcodeがちゃんと読んでくれるかどうかが気になるところです。しかし、これに関して良いメソッドを持っていれば、スムーズに対応可能かもしれませんが、現段階ではそれを探すのは少し難しいでしょう。
ケースを含まない正規表現を探そうとしている最中ですが、もし正規表現に詳しい方がいらっしゃったら教えてもらえると助かります。とりあえず、メモリレイアウトについてケースレスな正規表現が用いられているようです。
先ほど手に入れた情報では、コンビニエンスタイプのルールとして、ツイストリントを使うこともできると書いてありました。イナムレーション(列挙型)でも同様のことが言えるようです。これで、検出することもできるようですので、イナムレーションを使用し、ケースレスな列挙型を試してみましょう。
列挙型は具体的に存在していて、使用されたり意識されたりしています。これを活用することで得られる利点の一つとして、構造体をプラスにして、意味のないインスタンスを生成不能にするために、プライベートなイニシャライザを使うことがあります。前回も話しましたが、列挙型にすることで、イニシャライザを適切に定義しない限りは、存在しません。このようにケースが無い列挙型にすることで、無意味なインスタンスを生成するのを防ぐことができると言われています。
確かにメモリーレイアウトのインスタンスを生成できる可能性を完全に排除する観点から、ケースを持たない列挙型として設計することも一つの方法ですね。例えば、メモリーレイアウトを
MemoryLayout<Type>
のようにすることでサイズなどの管理も行えますが、ダイナミックなコードを書く際にはあまりインスタンスとして保持する必要はないかもしれません。このように設計次第でさまざまな選択肢がありますので、自分のプロジェクトに最適な方法を選ぶことが重要です。 例えば、メモリーレイアウトを考える際に、オブジェクトのメンバーを直接置く方法とインスタンス化して基本情報を持たせる方法、どっちが良いのか迷うことがありますね。例えば、サイズやアライメントを考えると、前者だと2回インスタンスを渡さないといけない場合があります。頻繁に使う場合は、インスタンスを取り出して使い回したほうが効率的かもしれませんが、場合によっては統一感を持たせて書いた方が良いです。メモリーレイアウトで違和感を感じることもありますが、一般的なオブジェクト指向のアイデアに従ってインスタンスを作り、そのインスタンスをエントリーポイントとして機能を呼び出す方法が理解されやすいです。一方で、名前空間にスタティックメンバーを揃えて関数を定義する方法もあります。このように、オブジェクト指向と手続き的なアプローチがありますが、どちらも良く使われる方法です。
クラスや構造体でインスタンスを作れますが、これは問題ではありません。関数を単純にグルーピングしたい場合は、クラスや構造体を使うとプライベートユニットにしたとしても、プライベートでイニシャライズされている可能性があります。ただし、そのファイル内に限られるため、深刻な問題にはなりません。定義を探す必要があるため、インスタンス化されていないか注意が必要です。
場合によってはエクステンションを使ってスタティックメンバーを追加することもできます。ケースレスなエクステンションを作るときには、一つの名前空間を作るためにスタティック変数を使うことができます。例えば:
enum MyNamespace {
static var value = 10
}
こうすることで、この列挙型は名前空間として機能し、意味のある名前空間を作ることができます。もちろん、ネームスペースとしての記述も可能です。
struct MyNamespace {
static var value = 10
}
このように、名前空間として使うことを明示すると、読みやすさが向上します。どちらの方法が適しているかは、プロジェクトの要件や開発の規約によるとも言えます。 そもそも推奨していないというのは、同じモジュールで名前が衝突するような作りをするな、という考え方じゃないですかね。なるほど、名前空間がもうモジュールでできてるから確かにそうですね。同じモジュール内でそんな名前が衝突するような作りを最初からしないように、というAppleの考え方かもしれませんね。ありえますね。その考え方としては確かにありますね。APIデザインガイドラインに明確な記述はないかもしれないけど、こういう考え方は確かにありそうです。メモリーレイアウトが微妙になることはありますけど、それはさておき、推奨されていない可能性は確かにありそうです。
今の話を聞くと、やっぱりあまり使いたくないな、と思えてきますね。パブリックなネームスペースキーワードの議論もされているようですが、これは長く議論されているので、後でじっくり見てみないといけませんね。ただ、便利だから使おうというのはやめたほうがいいと個人的には思います。プログラムに限らず、便利だからといって自分都合で使うと質の乱れが生じますからね。できるからやろうはちょっとやめたほうがいいです。そうするとこの便利さも半減しますけどね。要は、名前の衝突を避けるためだけに定義するのであれば、Swiftの
enum
が有用です。便利だからといって安易に使うのではなく、もう少し根拠を持って使うべきだと思います。次回は、実際にエースレスな
enum
を採用した話を見ることにしましょう。この辺りを具体的に見ていく予定です。根拠がしっかりしているものが欲しいですからね。今日はこれで終わりにします。また次回お会いしましょう。ありがとうございました。

