2010-02-04 posted at 10:57pm

instance_eval した時 Ruby 1.8 と Ruby 1.9 で定数の見え方が違った

環境

% ruby -v
ruby 1.8.6 (2008-08-11 patchlevel 287) [universal-darwin9.0]
% ruby1.9 -v
ruby 1.9.1p376 (2009-12-07 revision 26041) [i386-darwin9]

例えばこんなコードを実行すると Ruby 1.8 では "Bar" 、Ruby 1.9 では "Foo" が出力される。

class Foo
  CONST = 'Foo'
  def exec(&block)
    instance_eval(&block)
  end
end

class Bar < Foo
  CONST = 'Bar'
  new.exec { p CONST }
end

このコードの場合だと Ruby 1.8 だと "X" が出力されるが Ruby 1.9 では X::Const が見えず NameError になる。

class Foo
  def exec(&block)
    instance_eval(&block)
  end
end

module X
  CONST = 'X'
  class Bar < Foo
    new.exec { p CONST }
  end
end

Sinatra::Base を継承したクラスを書いてて (要は atomos なんだけど) before フィルターからそのクラスと同一モジュール下にあるクラスを参照しようとしてこれではまった。

あとこんなのも。

class Foo
  CONST = 'Foo'
  def exec(&block)
    instance_eval(&block)
  end
end

class Bar < Foo
  CONST = 'Bar'
  def exec(&block)
    super
    instance_eval(&block)
  end
  new.exec { p CONST }
end

Ruby 1.9 でこれを実行すると "Foo", "Foo" と出力されるのだけど、superinstance_eval(&block) の順序を逆にすると今度は実行結果が "Bar", "Bar" と変わってしまう (Ruby 1.8 ではいずれも "Bar", "Bar") 。さすがにキモい気がする。

追記

class Foo
  CONST = 'Foo'
  def exec1(&block)
    instance_eval(&block)
  end
  def exec2(&block)
    yield
  end
end

class Bar < Foo
  CONST = 'Bar'
  new.exec1 { p CONST } #=> "Bar" (1.8), "Foo" (1.9)
  new.exec2 { p CONST } #=> "Bar" (1.8, 1.9)
  new.instance_eval { p CONST } #=> "Bar" (1.8, 1.9)
end

Bar.new.exec1 { p CONST } #=> NameError (1.8), "Foo" (1.9)
Bar.new.exec2 { p CONST } #=> NameError (1.8, 1.9)
Bar.new.instance_eval { p CONST } #=> NameError (1.8), "Bar" (1.9)

Ruby 1.9 の instance_eval ではオブジェクトのコンテキストだけでスコープが定まり、他ではブロックの保持するスコープが使われているっぽい。

filed in