パターンマッチ

いま、ytljitというネイティブコードに落とせるVMを作っているのですが、Rubyにパターンマッチがないために時々悔しい思いをしています。日曜日にGaucheのコード(compile.scm)を読んでいてパターンマッチを上手に使って簡潔に書いてあるのを見て自分でも作ることにしました。もっとも、パターンマッチがあればプログラムが上手に簡潔になるかといえば、使う人次第というのは良く分かっているのですが…。

結構うまく行っているような気がするので、もうちょっとテストとかドキュメントとか整備してみんなに使ってもらえるようにしたいです。

現状のソースコードがここにあります。
http://gist.github.com/372413

まだ、不完全ですが、こんな感じで使います。

require 'matcher'

mat = Matcher.new
mat.pattern([:a, :b]) {|hash|
  p hash
}
mat.match([1, 2]) #-> {:a => 1, :b => 2}

まず、Matcherクラスのインスタンスを生成し、patternメソッドでパターンを登録します。patternメソッドはブロックをとります。このブロックはパターンがマッチした時に実行されます。引数のhashはハッシュテーブルでパターン中のシンボルをキーにそれに対応する値を格納しています。

パターンは複数個、指定できます。

  mat = Matcher.new
  mat.pattern([:a, :b]) {|hash|
    p hash
  }
  mat.pattern(:a) {|hash|
    p hash
  }
  mat.pattern([:a, [:b, :d]]) {|hash|
    p hash
  }
  mat.pattern([:a, [Array, :d], :c]) {|hash|
    p hash
  }
  mat.match([1, [2, 3]])  # -> {:a=>1, :b=>[2, 3]}
  mat.match([1, [2, 3], 4]) # -> {:a=>1, :d=>[2, 3], :c=>4}
  mat.match([1, [2, 3], 4, 5]) # -> {:a=>[1, [2, 3], 4, 5]}

複数個指定した場合はマッチするパターンの内1つが実行されます。どのパターンが実行されるかは未定義です。マッチする全部のパターンを実行するようにも出来るので、オプションとして選べるようにしようかと考えています。

 [Array, :d]

は特別な形式で、この位置にArrayクラスのデータが現れたらマッチして、:dにその値を入れるという意味になります。

パターンはRubyのプログラムに変換され、さらにバイトコードコンパイルします。matchを実行するときはキャッシュされたバイトコードを実行しますので普通にif文を並べた場合に比べてそれほど速度低下していないと思います。本当のところはまだベンチマークしていないので分からないのですが。また、複数のパターンがあって重複した比較はできるだけ省くようにコンパイルしています。将来的にはytljitでネイティブコードにコンパイルするのも面白いかなと考えています。