トップ 追記 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|
2014|05|

2014-05-31 (Sat)

))) test-unit-power_assertをリリース

Groovyなどいくつかの言語のテストフレームワークには、テスト失敗時にコードの各式の値を出力するPower Assertと呼ばれる機能があります。 今回、Ruby 2.0以上 + test-unit向けに同様のものを実装してみました(test-unit-power_assert)。

利用例は以下の通り。

require 'test/unit'
require 'test/unit/power_assert'
class MyTest < Test::Unit::TestCase
  def test_failed
    power_assert do
      "0".class == "3".to_i.times.map {|i| i + 1 }.class
    end
  end
end

power_assertメソッドにテスト対象の式をブロックとして与えるだけですが、出来れば-rオプションを使うなどしてテストコードが読み込まれる前にこのライブラリをrequireするようにしておくと情報の取りこぼしを防げます。

# 事前requireなし

        "0".class == "3".to_i.times.map {|i| i + 1 }.class
            |            |    |     |                |
            |            |    |     |                Array
            |            |    |     [1, 2, 3]
            |            |    #<Enumerator: 3:times>
            |            3
            String

# 事前requireあり

        "0".class == "3".to_i.times.map {|i| i + 1 }.class
            |     |      |    |     |                |
            |     |      |    |     |                Array
            |     |      |    |     [1, 2, 3]
            |     |      |    #<Enumerator: 3:times>
            |     |      3
            |     false
            String

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が取得できるので、大体のケースでうまく動く


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|
2014|05|
トップ 追記 RSS feed