C++ で variant とパターンマッチをほんのり実現する
OCaml から離れられない一つの原因が variant とパターンマッチである。関数型であるとかそういうことは、本質的にどうでもいいのだ。関数を引数に渡せたり、無名関数を使えたりしたら関数型だというのなら、Python は関数型ということになってしまう。むしろ、variant とパターンマッチ、この 2 つの機能があるから OCaml はやめられない。こういう人は多いはずだ*1。
説明する気も失せるほどなんてことはない内容である。ラベルでも貼れば?ということだ。パターンマッチが貧弱だが、必要最低限の機能はこれでどうにかなるだろう。
#define match(n) switch(n->label) #define with(label, var) case _##label: label* var = (label*)n->ptr; enum _Node { _Branch, _Leaf }; struct Base { virtual ~Base() {} }; template <typename T> struct Var { Var(_Node l, T* p) : label(l), ptr(p) {} ~Var() { delete ptr; } _Node label; T* ptr; }; struct Branch : public Base { Branch(Var<Base>* m1, Var<Base>* m2) : n1(m1), n2(m2) {} ~Branch() { delete n1; delete n2; } Var<Base> *n1; Var<Base> *n2; }; Var<Base>* make_Branch(Var<Base>* m1, Var<Base>* m2) { return new Var<Base>(_Branch, new Branch(m1, m2)); } struct Leaf : public Base { Leaf(const string& s) : str(s) {} string str; }; Var<Base>* make_Leaf(const string &s) { return new Var<Base>(_Leaf, new Leaf(s)); } void print(Var<Base>* n) { match(n) { with(Branch, b) { cout << '('; print(b->n1); cout << ','; print(b->n2); cout << ')'; } break; with(Leaf, l) { cout << l->str; } break; } } int main() { Var<Base>* n = make_Branch(make_Leaf("a"), make_Branch(make_Leaf("b"), make_Leaf("c"))); print(n); delete n; }
この解法のうれしいのは、-Wall すると、with を書き忘れたラベルをコンパイラが報告してくれるところだ。報告されれば直せばいい。処理を無視したいときは default で十分。さて、上記いくつかスタブ的なメソッドが散見されるのだが、これらを自動生成させるマクロをどうやって書けばいいかご存じの方は教えていただきたい。いろいろ考えたが、私の脳味噌ではどうにもならなかった。
余談だが、OO の方はこういう問題に対して Visitor パターンを適用したくなると思うのだが、アレは牛刀だなぁというのが私の印象。variant したい型が増えたり、単純に木をたどったり変換したり以外の少し凝ったたどり方をしようとしたとたん、ベースクラスからいじり直さないといけないのがどうにもメンドクサイ。カプセル化は必要なときにすればいい。必要ないならしなければいい。なんでも一つの解法で解こうとするのは非効率だと思う。
*1:条件付き確率である