Nemerle のパターンマッチ


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));
}

この例では headstring 型であることを明示しないと,型推論ができずにコンパイルエラーになります。ただし, 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" を追加。

脚注

  1. Some("") を返した方がマッチした感じがして良いと思うのですが,どうしてこういう設計になっているのでしょう。 []