Hi,
I’m trying to understand if/how/when/why Ruby implicitly passes
blocks to
instance_eval, class_eval and eval, but not to plain methods.
It seems that 1.9 behavior is consistent across all versions of
eval: a
block, passed into a method that does an eval, is available for use
by
that eval. It also seems that 1.8.x has a bug in that the first
level
method called does not make the block available to eval, but that
it is available to more deeply nested method calls.
class Mystery1
def level_1(&block)
level_2(&block)
# 1.8.x: LocalJumpError ; 1.9.x: Ok
puts "Mystery1 level_1: #{instance_eval('yield')}"
end
def level_2(&block)
level_3(&block)
puts "Mystery1 level_2: #{instance_eval('yield')}" # OK
end
def level_3(&block)
puts "Mystery1 level_3: #{instance_eval('yield')}" # OK
end
end
o = Mystery1.new
begin
o.level_1 { 12 }
rescue Exception => e
puts "#{o.class} raised #{e.class}: #{e.message}"
p e.backtrace
end
Running this against 1.8.[67] and 1.9.2 gives different results:
ruby-1.8.6-tv1_8_6_398: ruby 1.8.6 (2010-02-04 patchlevel 398)
[i686-darwin9.8.0]
Mystery1 level_3: 12
Mystery1 level_2: 12
Mystery1 raised LocalJumpError: (eval):1:in `level_1': no block
given
["/Users/pmclain/tmp/m1.rb:6:in `level_1’", “/Users/pmclain/tmp/
m1.rb:21”]
ruby-1.8.7-p249: ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-
darwin9.8.0]
Mystery1 level_3: 12
Mystery1 level_2: 12
Mystery1 raised LocalJumpError: (eval):1:in `level_1': no block
given
["/Users/pmclain/tmp/m1.rb:6:in `level_1’", “/Users/pmclain/tmp/
m1.rb:21”]
ruby-1.9.2-tv1_9_2_preview2: ruby 1.9.2dev (2009-09-07 trunk
-
[i386-darwin9.8.0]
Mystery1 level_3: 12
Mystery1 level_2: 12
Mystery1 level_1: 12
Question 1: Is the 1.8 behavior a bug?
Question 2: Is the 1.9 behavior intended? If so, is it documented
anywhere? Perhaps the various eval methods should document that
they pass
the calling frame’s block to the eval.
If I replace “instance_eval” with “Mystery1.class_eval”, I get the
same
result. If I replace “instance_eval” with “eval”, I get different
results:
ruby-1.8.6-tv1_8_6_398: ruby 1.8.6 (2010-02-04 patchlevel 398)
[i686-darwin9.8.0]
Mystery1 raised LocalJumpError: (eval):1:in `level_3': no block
given
["/Users/pmclain/tmp/m1.rb:15:in level_3'", "/Users/pmclain/ tmp/m1.rb:10:in
eval’", “/Users/pmclain/tmp/m1.rb:15:in level_3'", "/ Users/pmclain/tmp/m1.rb:10:in
level_2’”, “/Users/pmclain/tmp/m1.rb:
3:in `level_1’”, “/Users/pmclain/tmp/m1.rb:21”]
ruby-1.8.7-p249: ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-
darwin9.8.0]
Mystery1 raised LocalJumpError: (eval):1:in `level_3': no block
given
["/Users/pmclain/tmp/m1.rb:15:in level_3'", "/Users/pmclain/ tmp/m1.rb:10:in
eval’", “/Users/pmclain/tmp/m1.rb:15:in level_3'", "/ Users/pmclain/tmp/m1.rb:10:in
level_2’”, “/Users/pmclain/tmp/m1.rb:
3:in `level_1’”, “/Users/pmclain/tmp/m1.rb:21”]
ruby-1.9.2-tv1_9_2_preview2: ruby 1.9.2dev (2009-09-07 trunk
-
[i386-darwin9.8.0]
Mystery1 level_3: 12
Mystery1 level_2: 12
Mystery1 level_1: 12
So it looks like 1.9 is consistent in making the block available to
eval
at all levels, but 1.8 treats eval differently than class_eval and
instance_eval.
It also looks like 1.8 has another bug in that it tries too hard to
pass blocks along, whereas 1.9 gives reasonable behavior. In
Mystery2 I
don’t pass the block from level_2 to level_3, and 1.9 complains
that no
block was given; 1.8 seems to pass the block implicitly…
class Mystery2
def level_1(&block)
level_2(&block)
# 1.8.x: LocalJumpError ; 1.9.x: Ok
#puts "Mystery2 level_1: #{instance_eval('yield')}"
end
def level_2(&block)
level_3
puts "Mystery2 level_2: #{instance_eval('yield')}" # OK
end
def level_3
puts "Mystery2 level_3: #{instance_eval('yield')}" # 1.8:
OK 1.9: LocalJumpError
end
end
o = Mystery2.new
begin
o.level_1 { 12 }
rescue Exception => e
puts "#{o.class} raised #{e.class}: #{e.message}"
p e.backtrace
end
And the output:
ruby-1.8.6-tv1_8_6_398: ruby 1.8.6 (2010-02-04 patchlevel 398)
[i686-darwin9.8.0]
Mystery2 level_3: 12
Mystery2 level_2: 12
ruby-1.8.7-p249: ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-
darwin9.8.0]
Mystery2 level_3: 12
Mystery2 level_2: 12
ruby-1.9.2-tv1_9_2_preview2: ruby 1.9.2dev (2009-09-07 trunk
-
[i386-darwin9.8.0]
Mystery2 raised LocalJumpError: no block given (yield)
["/Users/pmclain/tmp/m2.rb:15:ininstance_eval'", "/Users/ pmclain/tmp/m2.rb:15:in
instance_eval’", “/Users/pmclain/tmp/m2.rb:
15:inlevel_3'", "/Users/pmclain/tmp/m2.rb:10:in
level_2’”, “/Users/
pmclain/tmp/m2.rb:3:inlevel_1'", "/Users/pmclain/tmp/m2.rb:21:in
'”]
Question 3: Is the 1.8 behavior a bug?
Question 4: Is the 1.9 behavior correct?
Is there any documentation (other than the code) that describes
this in
detail? Neither the Pick Axe book, nor the Flanagan/Matz book give
detail on this. The RubySpecs also seem silent on this.
–
Peter McLain
[email protected]