先日ポストしたScalaっぽいパターンマッチをRubyで実装するをベースに 一通り機能を揃えてライブラリ化した(pattern-match)。
面白そうなパターンをいくつか例に取ってみると、まず多重代入。
match([0, [1, 2, 3, 4]]) { with(_[a, _[b, *c, d]]) { # `Array.(a, Array.(b, *c, d))'と同じ p [a, b, c, d] #=> [0, 1, [2, 3], 4] } }
Gaucheのutil.match由来の___、__k。
match([[0, 1], [2, 3]]) { with(_[_[a, b], ___]) { p [a, b] #=> [[0, 2], [1, 3]] } }
赤黒木のbalance(参考までにScala版実装)。
Node = Struct.new(:left, :key, :right) class R < Node; end class B < Node; end def balance(left, key, right) match([left, key, right]) { with(_[R.(a, x, b), y, R.(c, z, d)]) { R[B[a, x, b], y, B[c, z, d]] } with(_[R.(R.(a, x, b), y, c), z, d]) { R[B[a, x, b], y, B[c, z, d]] } with(_[R.(a, x, R.(b, y, c)), z, d]) { R[B[a, x, b], y, B[c, z, d]] } with(_[a, x, R.(b, y, R.(c, z, d))]) { R[B[a, x, b], y, B[c, z, d]] } with(_[a, x, R.(R.(b, y, c), z, d)]) { R[B[a, x, b], y, B[c, z, d]] } with(_) { B[left, key, right] } } end
特殊なパターンとしてDuck Typing的なことも出来るようにしてみた。ただ、実用性があるかというと、どうなんだろう……。
match(10) { with(Object.(:to_i => a, :foobar => b)) { :not_match } with(Object.(:to_i => a, :to_s.(16) => b)) { p [a, b] #=> [10, "a"] } }
ちなみに、ruby-trunk - Feature #4085 : Refinements and nested methodsをサポートする環境であれば、 次のような書き方も出来るようになっている*1。Refinements素晴らしい。
def balance(left, key, right) match([left, key, right]) { with(_[R[a, x, b], y, R[c, z, d]]) { R[B[a, x, b], y, B[c, z, d]] } with(_[R[R[a, x, b], y, c], z, d]) { R[B[a, x, b], y, B[c, z, d]] } with(_[R[a, x, R[b, y, c]], z, d]) { R[B[a, x, b], y, B[c, z, d]] } with(_[a, x, R[b, y, R[c, z, d]]]) { R[B[a, x, b], y, B[c, z, d]] } with(_[a, x, R[R[b, y, c], z, d]]) { R[B[a, x, b], y, B[c, z, d]] } with(_) { B[left, key, right] } } end
*1 r29944向けパッチで動作確認済み
赤黒木のdeleteを実装して気付いたのですが、matchがネストすると外側のパターンマッチで得た変数(実際はメソッド)を内側で参照できないようです。 <br>あと、以下のコードではなぜかundefined local variable or method `x'になってしまいました。RedNode[app(a, b), x, xv, c]の部分を別メソッドにすると大丈夫でした。 <br> <br> with _[a, RedNode.(b, x, xv, c)] do <br> RedNode[app(a, b), x, xv, c] <br> end <br> <br>参考: https://gist.github.com/1993507
> matchがネストすると外側のパターンマッチで得た変数(実際はメソッド)を内側で参照できないようです。 <br> <br>外側で得た変数を内側のwithの引数として渡しても、値の参照ではなく常にvariable pattern相当になってしまうということですよね。 <br> <br>最終的にはこれらの変数はレキシカルスコープなものにしたいと思っていますが、 <br>まだ方法を考え中のため簡易実装でお茶を濁しているのが現状です。 <br> <br>修正されるまでの間は、一旦ローカル変数に代入してそちらを使うようにするといった逃げ方をしてもらう必要があります。 <br> <br> <br>> 以下のコードではなぜかundefined local variable or method `x'になってしまいました。 <br> <br>使っているRubyのバージョンを教えていただけないでしょうか。 <br>以下のコードで再現を試みましたが、 <br> <br> ruby -e 'load "red_black.rb"' -e ' <br> def app(*); end <br> <br> match([0, RedBlack::RedNode[1, 2, 3, 4]]) do <br> with _[a, ::RedBlack::RedNode.(b, x, xv, c)] do <br> RedBlack::RedNode[app(a, b), x, xv, c] <br> end <br> end <br> ' <br> <br>次のいずれのバージョンでもエラーとなりませんでした。 <br> <br> ruby 1.9.2p290 (2011-07-09) [x86_64-linux] <br> ruby 1.9.3p125 (2012-02-16 revision 34643) [x86_64-linux] <br> ruby 2.0.0dev (2012-03-08 trunk 34947) [x86_64-linux] <br>
> 外側で得た変数を内側のwithの引数として渡しても、値の参照ではなく常にvariable pattern相当になってしまうということですよね。 <br> <br>いえ、 withの引数ではなくブロック内で参照してもエラーになったのですが、matchのネストは関係なくて、もう一つのエラーと同根かもしれません。 <br> <br>> 次のいずれのバージョンでもエラーとなりませんでした。 <br> <br>バージョンはruby 2.0.0dev (2012-02-26 trunk 34819) [i686-linux]ですが、単純なケースでは再現しないかもしれません。 <br>以下のコードだと再現します。 <br> <br>https://gist.github.com/2004685 <br> <br>169行目のif falseをif trueにしてRedNode[app(a, b), x, xv, c]を <br>別のメソッドで実行するようにすればエラーが出なくなります。 <br>
なるほど、分かりました。 <br>やはり両方ともmatchのネストが原因のようです。 <br> <br>GitHub上のmasterに修正をコミットしておきました。 <br>red_black_error.rbが通ることは確認済みです。 <br>
ありがとうございました。後で確認してみます