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" と出力されるのだけど、super と instance_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 ではオブジェクトのコンテキストだけでスコープが定まり、他ではブロックの保持するスコープが使われているっぽい。