概要
- C#の勉強中にパターンという文法を知り面白いと思ったという小話
経緯
- ゲーム開発において、C#はUnityやGodotなどのゲームエンジンを用いた開発で使われる主要なプログラミング言語である
- ゲーム開発に興味のある筆者は、Godotを使ったゲーム開発と並行してC#の勉強をしている
- 元々C++の方が馴染みがありSiv3Dでゲーム開発をしていたが、本格的なゲームエンジンを使ったゲーム開発も経験してみたいという思いからGodotに手を出した
- ただC#に今まで触れたことがなかったため、これを機に勉強を始めた
- Godotを使うならGDScript、C++かつゲームエンジンを使った開発をしたいならUnrealEngineという選択肢もあるが、折角なので新しい言語を勉強してみたいという欲が勝った
- 手始めに、オライリーサブスクで読める『プログラミングC# 第8版』を読んでいる
- オライリーサブスクについては過去記事にも記載あり
- 序盤の方でパターンという文法が出てきて、少し興味深いと感じたのでメモがてら本記事を執筆している
- あくまで初学者のメモに過ぎず、正確さを保証するものではないし、まして目新しさのある内容でもないことは留意していただきたい
本題
C#におけるパターンとは
※以下筆者の解釈なので、正しい情報を知りたい人はちゃんと本を読みましょう
- C++にもある switch-case 文を拡張して使いやすくしたようなものか
- 本書でも「switch文でのみ使われるものではない」と但し書きはされていたが
- C/C++ で今まで自分が読み書きしてきた switch-case文は、以下のようにcaseラベルの値に応じて条件分岐するというシンプルなものだった
- そして、ラベルに使用できるのは
int
やchar
など比較的シンプルな型のみである(と筆者は理解している)
switch (choice) { case 1: cout << "Option 1 selected" << endl; break; case 2: cout << "Option 2 selected" << endl; break; case 3: cout << "Option 3 selected" << endl; break; default: cout << "Invalid option" << endl; }
- そして、ラベルに使用できるのは
- しかし、C# では以下のようにタプルをcaseのラベルに相当するものとして使用できる
- 他のモダンな言語に触れている人からすると何でもない機能なのかもしれないが、C++ばっか触ってきた身には新鮮に感じる
switch(choice)
{
case (0,0):
Console.WriteLine("Origin");
break;
case (0,1):
case (1,0):
Console.WriteLine("Unit Vector");
break;
case (1,1):
Console.WriteLine("Square");
break;
}
- 更に、型パターンや位置指定パターン、破棄パターンなんてものもあるらしい
- ちなみに、上述の例は定数パターンであるとのこと。確かに (0,0) などは定数で、それにマッチするパターンということなのだろう
- 本書を読んでも各パターンの厳密な語義は良く分からなかったので、雰囲気で捉えたニュアンスのまま以下に例を挙げる
// 以下のコードでは switch 部分は省略する // 下記のコードをそのままコピペして動く保証はない。あくまで解説用のコード // 型パターン case string s: Console.WriteLine($"文字列: {s}"); break; case int i: Console WriteLine($"整数 {i}"); break; // 型パターン + 位置指定パターン case (int x, int y): Console.WriteLine($"座標: {x},{y}"); break; // 破棄パターン + 位置指定パターン case (int x, _): Console.WriteLIne($"x座標: {x}"); break;
- まず、型パターンはその名の通り、ラベルとして与えられた変数の型を見て分岐するパターン
- 次に、位置指定パターンでは、タプルのようなコレクションについて特定の位置の要素にフォーカスする。上記の例だと0番目と1番目の要素について、それぞれint型であることをパターンにマッチする条件としている
- 最後に、破棄パターンでは、タプルのようなコレクションについて特定の値だけをパターンマッチの条件にする。上記の例だとタプルの0番目の要素がint型であることだけをパターンマッチの条件としていて、1番目の要素については関知しないということになる
さらに発展系
- 更に、switch式をreturnしたり、switchを使わずisキーワードとパターンを組み合わせるような例もある
// こちらも単体で動くことを想定していない解説用のコード // switch式のreturn return shape switch { (int w, int h) when w < h => "Portrait(Vertical)", (int w, int h) when w > h => "Landscape(Horizontal)", (int _, int _) => "Square", _ => "Invalid" }; // isキーワード if (value is (int x, int y)) { Console.WriteLine($"x座標: {x}, y座標: {y}"); }
- 1つ目の例は、四角形の横幅と高さのint値を持つ要素数が2つのタプルについて、縦長か横長か正方形かを判定して判定結果をreturnするコードである
- 要素数や型の情報が合わない場合は、いずれのパターンにもマッチせず "Invalid" がreturnされる
- 2つ目の例は、
is
キーワードというものを使って value が int型の要素を2つもつタプルであるかどうかをチェックしている- value が int型の要素を2つもつタプルである場合、if内の条件式は
true
を返し、座標情報が出力される
- value が int型の要素を2つもつタプルである場合、if内の条件式は
- 1つ目の例は、四角形の横幅と高さのint値を持つ要素数が2つのタプルについて、縦長か横長か正方形かを判定して判定結果をreturnするコードである
雑感
- 以上は、C#初学者が文法書を眺めて面白いと思った文法である「パターン」を備忘録も兼ねて文書化したものである
- よって本記事は筆者の勉強用に執筆したものだが、同様にC#を学び始めている方の助けになれば幸いである
- パターンマッチという単語は関数型プログラミング言語の文脈で耳にするような印象を筆者は抱いている。C#のパターンマッチも関数型のパラダイムから輸入してきたもののような予感がしているが、調べていないので真偽のほどは不明
- もしかしたら関数型言語ではもっと高度なパターンマッチが考案されいているのかもしれない。C#の勉強が落ち着いてきたら関数型プログラミング言語にも手を出してみようかな