トップ 追記 RSS feed

継続にっき

2004|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|12|
2006|01|03|05|06|08|09|10|12|
2007|01|02|03|05|07|12|
2008|10|
2009|01|05|12|
2010|04|05|11|
2011|01|09|12|
2012|02|03|05|09|12|
2013|02|03|

2013-03-23 (Sat)

))) MathematicaっぽいパターンマッチをRubyで実装する

ScalaっぽいパターンマッチをRubyで実装するから始まった 一連のpattern-matchライブラリ関連エントリのおそらく最終章。 任意のオブジェクトに対して正規表現相当のマッチをできるようにしてみたのでその紹介です。

どういうことかと言うと、例えば配列をFixnumの前後で分割したりとか

match([:a, 0, :b, :c]) do
  with(_[*a, Fixnum, *b]) do
    a #=> [:a]
    b #=> [:b, :c]
  end
end

連続した要素の積が12になるところを探したりとか

match([1, 2, 3, 4, 5]) do
  with(_[*_, *a, *_], guard { a.inject(:*) == 12 }) do
    a #=> [3, 4]
  end
end

「シーケンス中で連続して同じ値が入っている各箇所について,2 個目以降は削除したシーケンス」を取得したり (前後の値も利用したシーケンス処理 - NyaRuRuが地球にいたころ)とか

def replace_repeated(obj, &block)
  ret = match(obj, &block)
  if ret == obj
    ret
  else
    replace_repeated(ret, &block)
  end
rescue PatternMatch::NoMatchingPatternError
  obj
end

replace_repeated([1, 2, 4, 4, 3, 3, 4, 0, 0]) do
  with(_[*a, x, x, *b]) { [*a, x, *b] }
end #=> [1, 2, 4, 3, 4, 0]

こういった処理が簡単に書けるようになりました。

先日のRuby開発者会議でパターンマッチを入れたいねみたいな話をしてきたのですが、 せっかくならこのぐらい出来るようになると楽しそうです。


2013-02-09 (Sat)

))) Rubyの例外終了時に自動でREPLを起動する(2)

フィルタコマンドを作るときなどに Better Errors の様な感じで使えるREPLが欲しくなって、 ライブラリを作ってみたことがあります(Rubyの例外終了時に自動でREPLを起動する)。 *1

その時は、set_trace_funcのオーバーヘッドを避けるためにStandardError#initializeをCレベルで定義して その中でbindingを取得する*2という ややトリッキーな実装をしていたのですが、Ruby 2.0でset_trace_funcの高速版といえるTracePointが導入されたのでそちらを使って書き直してみました。

主な変更点は2点。

  • 例外発生直前のトレース情報を出力するようにした
    • メソッドチェインの途中の値の確認などが簡単に
  • Pure Rubyになった
    • Windowsでも楽に使えるように

gem installしてrequireするだけで利用できます(当然、1.9では動きません)。

$ gem install dexc

$ cat t.rb
def m(obj)
  obj.to_s + 1
end

m(0)

$ ruby -rdexc t.rb
 0:lib/dexc.rb:82>     tp.enable
   TracePoint#enable: false
 1:lib/dexc.rb:83>   end
   Dexc#start: false
 2:lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45>       return gem_original_require(path)
   Kernel#gem_original_require: true
 3:lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:45>       return gem_original_require(path)
   Kernel#require: true
 4:t.rb:0>
   IO#set_encoding: #<File:t.rb (closed)>
 5:t.rb:0>
   IO#set_encoding: #<File:t.rb (closed)>
 6:t.rb:1> def m(obj)
   Module#method_added: nil
 7:t.rb:2>   obj.to_s + 1
   Fixnum#to_s: "0"
 8:t.rb:2>   obj.to_s + 1
   Exception#initialize: #<TypeError: no implicit conversion of Fixnum into String>
 9:t.rb:2>   obj.to_s + 1
   Class#new: #<TypeError: no implicit conversion of Fixnum into String>
10:t.rb:2>   obj.to_s + 1
   Exception#exception: #<TypeError: no implicit conversion of Fixnum into String>
11:t.rb:2>   obj.to_s + 1
   Exception#backtrace: nil

TypeError: no implicit conversion of Fixnum into String
        from t.rb:2:in `+'
        from t.rb:2:in `m'
        from t.rb:4:in `<main>'

From: t.rb @ line 2 Object#m:

    1: def m(obj)
 => 2:   obj.to_s + 1
    3: end

[1] pry(main)> obj
=> 0
[2] pry(main)> hist[7] # or dexc_hist[7]
=> "0"

例外が起きた環境でREPL(例ではPry)が起動されています。 また、`hist'を使うことで例外発生直前の一連のメソッドの返り値を取得できることが分かります。

なお、トレースが取れるようになって便利になった反面パフォーマンスは低下しました。 参考までに、Rubyに含まれているテスト(test/ruby)を走らせた時の所要時間を比較すると以下のようになります。

条件所要時間
dexcなし2:25.83
dexcあり(トレース無効(:raiseのみ))2:28.90
dexcあり(トレース有効)5:34.07

*1 実際には順序が逆で、実装方式を思いついたからライブラリを作ったというのが正しいのですが

*2 こうすると例外オブジェクト生成時に(Rubyレベルで)直近のbindingが取得できるので、大体のケースでうまく動く


2012-12-16 (Sun)

))) RubyプログラムをVM命令単位で実行する

Ruby VM アドベントカレンダー が面白いので、 便乗してVMをテーマに書いてみます。

Ruby(CRuby)は1.9から評価器がVM化され、RubyプログラムはVM命令にコンパイルされてから 動作するようになりました。コンパイル結果は、rubyコマンドに--dump=insnsオプションを 与えることで確認することができます。

$ ruby --dump=insns -e '"abc"+"def"'
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
0000 trace            1                                               (   1)
0002 putstring        "abc"
0004 putstring        "def"
0006 opt_plus         <callinfo!mid:+, argc:1, ARGS_SKIP>
0008 leave

ここでは各VM命令の実行によってVMがどういう動きをするのか見る方法を紹介します。

やり方はいろいろあると思いますが、VM命令の先頭に 「VMの状態を出力してからTRAPを飛ばす」コードを 追加していくことにしましょう。

まず、rubyに以下のパッチを当ててビルドします。

gdb経由で実行してみます。(TRAPを飛ばすために環境変数ENABLE_TRAPを定義しておく必要があります)

$ ENABLE_TRAP= gdb --args ./ruby --disable-gems -e '"abc"+"def"'
...
(gdb) r
...
-- stack frame ------------
0000 (0x7ffff6a09010): 00000008
0001 (0x7ffff6a09018): 555555a43d00
0002 (0x7ffff6a09020): 00000008
0003 (0x7ffff6a09028): 555555a39710
-- Control frame information -----------------------------------------------
c:0002 p:0000 s:0004 e:000858 EVAL   -e:1 [FINISH]
c:0001 p:0000 s:0002 e:000fa8 TOP    [FINISH]

### Next: trace ################
Program received signal SIGTRAP, Trace/breakpoint trap.
...
(gdb)

最初のVM命令であるtraceの実行直前のVMの状態(スタックフレームとコントロールフレーム)が出力されました。 ここでcontinue(c)と入力すると、

(gdb) c
Continuing.
-- stack frame ------------
0000 (0x7ffff6a09010): 00000008
0001 (0x7ffff6a09018): 555555a43d00
0002 (0x7ffff6a09020): 00000008
0003 (0x7ffff6a09028): 555555a39710
-- Control frame information -----------------------------------------------
c:0002 p:0002 s:0004 e:000858 EVAL   -e:1 [FINISH]
c:0001 p:0000 s:0002 e:000fa8 TOP    [FINISH]

### Next: putstring ################
Program received signal SIGTRAP, Trace/breakpoint trap.
(gdb)

と、trace実行後の状態が出力されます。

続けてputstringを実行すると、

(gdb) c
Continuing.
-- stack frame ------------
0000 (0x7ffff6a09010): 00000008
0001 (0x7ffff6a09018): 555555a43d00
0002 (0x7ffff6a09020): 00000008
0003 (0x7ffff6a09028): 555555a39710
0004 (0x7ffff6a09030): 555555a396e8
-- Control frame information -----------------------------------------------
c:0002 p:0004 s:0005 e:000858 EVAL   -e:1 [FINISH]
c:0001 p:0000 s:0002 e:000fa8 TOP    [FINISH]

### Next: putstring ################
Program received signal SIGTRAP, Trace/breakpoint trap.
(gdb)

スタックに値が追加されました。

rubyのソースツリーに含まれている.gdbinitには、 VALUEを与えるとそれをRubyオブジェクトとして出力するrpというコマンドが 定義されているのですが、それを使うと、

(gdb) rp 0x555555a396e8
T_STRING: "abc" bytesize:3 (embed) encoding:1 coderange:7bit $1 = (struct RString *) 0x555555a396e8

追加された値が"abc"であることが分かります。

興味がある人は、メソッド/ブロック呼び出しなどいろいろなコードを動かしてみると面白いかもしれません。


2004|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|12|
2006|01|03|05|06|08|09|10|12|
2007|01|02|03|05|07|12|
2008|10|
2009|01|05|12|
2010|04|05|11|
2011|01|09|12|
2012|02|03|05|09|12|
2013|02|03|
トップ 追記 RSS feed