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("")
を返した方がマッチした感じがして良いと思うのですが,どうしてこういう設計になっているのでしょう。 [↩]