Nemerle 1.1 のパターンマッチをまとめました。
リテラルパターン
もっとも基本的なパターンです。
プリミティブ
match (n)
{
| 1 => "one";
| 2 => "two";
| _ => "more";
}
match (s)
{
| "one" => 1;
| "two" => 2;
| _ => 0;
}
タプル
match (t)
{
| (1, "foo") => 1;
| (2, _) => 2; // 部分マッチ
| (_, "bar") => 3;
| (_, _) => 4; // (_, _) の代わりに _ でも OK
}
列挙型
enum Count
{
| One
| Two
}
match (e)
{
| Count.One => 1;
| Two => 2; // 型名は省略できる
}
バリアント型
variant Value
{
| Int { i : int }
| Str { s : string }
| IntStr { i : int;
s : string }
}
match (v)
{
| Int(i = 1) => 1
| Int(i = _) => 2 // ワイルドカード
| Str("foo") => 3 // フィールド名は省略可能
| Str(_) => 4 // フィールド名を省略したワイルドカード
| IntStr(0, _) => 5 // 部分マッチ
| IntStr(s = "bar") => 6 // 省略したフィールドはワイルドカード扱い
| IntStr => 7 // 全フィールドに対するワイルドカードでは () も省略可能
}
レコードパターン
特定の名前のフィールドやプロパティに対してマッチングを行えます。
[Record]
using Nemerle.Utility;
class Person
{
public name : string;
[Accessor]
public age : int;
}
match (p)
{
| Person where (name = "Alice") => "Alice";
| Person(age = 0) => "baby"; // where は省略可能
| (name = "Bob") => "Bob"; // さらに型名も省略可能
| (Age = 10) => "10-year-old kid"; // プロパティでのマッチ
| ("Charlie", _) => "Charlie"; // コンストラクターとフィールドが一致するのでフィールド名を省略可能
| _ => "Stranger";
}
variant 型でのパターンマッチのような表現も可能です。
class Person
{
public name : string;
}
class Animal
{
public name : string;
}
match (o : object)
{
| Person => "Person";
| Animal where (name = "Pochi") => "Animal, Pochi";
| _ => "Strange animal";
}
リストパターン
リストに対してマッチします。 [] でリストを,:: でリストの連結を表現できます。
match (l) {
| [1, 2] => 1 // 1 と 2
| [1, _] => 2 // 1 と何か
| [_, 2] => 3 // 何かと 2
| [_, _] => 4 // 要素が 2 つ
| _ :: 2 :: _ => 5 // 2 番目の要素が 3
| [] => 6 // 空リスト
| _ => 7
}
_ :: 3 :: _ の最後のワイルドカードは空リストにもマッチするので,長さが 2 のリストにもマッチします。しかしこの例では長さ 2 のリストは直前の [_, _] にマッチするので,結局ここまで到達しません。
変数パターン
パターンマッチに変数を導入して後で参照できます。
def t = (1, "foo");
match (t)
{
| (i, s) => $"Int=$i and Str=$s";
}
def getLength(l)
{
| [] => 0;
| _ :: tail => 1 + getLength(tail);
}
_ = getLength([1, 2, 3, 4, 5]); // 5
as パターン
マッチさせるオブジェクトに変数を導入します。
def get() { (1, "foo") }
match (get())
{
| _ as t => WriteLine($"get() returns $t");
}
when パターン
when で条件を加えることができます。このパターンはなぜか公式 Wiki のパターンマッチの項目に載っていないのですが,当たり前のように使われます。
def square(x) { x * x }
def pow(x, n)
{
| _ when n == 0 => 1;
| _ when n % 2 == 0 => square(pow(x, n / 2));
| _ => x * pow(x, n - 1);
}
型チェックパターン
is を使って型をチェックすることができます。変数名を導入すれば後で参照可能です。
def check(o : object)
{
| i is int => $"Int($i)";
| _ is string => "Some String";
| _ => "Other";
}
_ = check(3); // Int(3)
_ = check("foo"); // Some String
_ = check(1.5) // Other
型ヒントパターン
型情報を明示的に与えてやることで,型推論を可能にします。
def initials(l)
{
| [] => "";
| (head : string) :: tail => string.Format("{0}.{1}", char.ToUpper(head[0]), initials(tail));
}
この例では head が string 型であることを明示しないと,型推論ができずにコンパイルエラーになります。ただし, initials が後で次のように呼ばれていた場合は initials の型が list[string] -> string と推論されるので, head の型を明示しなくてもコンパイルできます。
_ = initials(["Hudson", "Kreitman", "Aguadé"]); // H.K.A.
または, def initials(l : list[string]) のように引数に型情報を与えてやる方法もあります。
alternative clauses
複数のパターンに対して 1 つの結果が要求される場合,パターンを | で区切って並べてやることで,結果の記述の重複を防ぐことができます。
match (c)
{
| 'a' | 'e' | 'i' | 'o' | 'u' => "vowel";
| _ => "consonant";
}
with clauses
パターン内でデフォルト値を定義することができます。
def foo(_)
{
| [x] with (y = 4, z = 5)
| [x, y] with z = 5
| [x, y, z] => x * y * z
| _ => 42
}
リストの要素が 1 つの場合は 2 番目の要素を 4 とし,リストの要素が 2 つ以下のときは 3 番目の要素を 5 として, 1 つ目と 2 つ目と 3 つ目の要素の積を返します。要素数が 0 または 4 以上のときは 42 になります。
正規表現パターン
regexp を付けることで,正規表現をパターンに使えます。
using Nemerle.Text;
regexp match (s)
{
| @"\d+" => Some("number");
| @"(?<name>foo|bar)?" => name;
| _ => Some("other");
}
Nemerle の正規表現では,量化子の ? や * の結果は option 型になり,長さ 0 にマッチした場合に None になります。なので上記のパターンマッチは s が空文字列のときに 2 番目のパターンにマッチし,結果として None を返します[A]。
アクティブパターン
[2012-07-03 10:30 追記] Nemerle.ActivePatterns.dll を参照することで,アクティブパターンが使えます。
using Nemerle.ActivePatterns;
def Sum(a : int, b : int) : option[int]
{
Some(b - a);
}
active match (5)
{
| Sum(2, x) => System.Console.WriteLine(x);
| _ => throw AssertionException();
}
マッチングの部分のみに着目すると,大体次のように展開されます。
def rest(x)
{
match(x)
{
| _ => throw AssertionException();
}
}
match (5)
{
| y => match (Sum(2, y))
{
| Some(x) => System.Console.WriteLine(x);
| _ => rest(5);
}
| _ => rest(5);
}
Sum(2, y) つまり Some(5 - 2) なので x には 3 が入り,これがコンソールに出力されます。 Sum なのに実装が足し算になっていないのはこういう理由からです。しかしパターンマッチから見ると, 2 と何かを足したら 5 になるような値にマッチするというわかりやすい形になります。
まとめ
Nemerle は他の言語のパターンマッチに加え,拡張された強力なパターンマッチも持っています。
参考文献
For Further Study
更新履歴
- [2012-07-03 10:30 追記] アクティブパターンを追加。
- [2012-07-20 05:50 追記] "For Further Study" を追加。
脚注
Some("")を返した方がマッチした感じがして良いと思うのですが,どうしてこういう設計になっているのでしょう。 [↩]