From: Peter McLain Date: 2010-02-06T03:04:32+09:00 Subject: eval and implicit passing of blocks 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 24787) [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 24787) [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 24787) [i386-darwin9.8.0] Mystery2 raised LocalJumpError: no block given (yield) ["/Users/pmclain/tmp/m2.rb:15:in `instance_eval'", "/Users/ pmclain/tmp/m2.rb:15:in `instance_eval'", "/Users/pmclain/tmp/m2.rb: 15:in `level_3'", "/Users/pmclain/tmp/m2.rb:10:in `level_2'", "/Users/ pmclain/tmp/m2.rb:3:in `level_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 peter.mclain@gemstone.com