instance_eval的块参数 - 记录?目的?

ing*_*ger 7 ruby metaprogramming

刚刚意识到将instance_evalyield self作为相关块的参数(除了1.9.2版本中的错误:http://www.ruby-forum.com/topic/189422 )

1.9.3p194 :003 > class C;end
1.9.3p194 :004 > C.new.instance_eval {|*a| a}
 => [#<C:0x00000001f99dd0>] 
1.9.3p194 :005 > 
Run Code Online (Sandbox Code Playgroud)

这是在某处记录/指定的吗?看看ruby-doc:BasicObject,看不到任何提到的块参数.

是否有一个理由 - 从一些纯粹的历史性的那个 - 用于明确地传递它,无论如何它总是被定义?


我受此打击的方式是:

l = lambda {  }
myobj.instance_eval(&l)  # barks
Run Code Online (Sandbox Code Playgroud)

这在1.8.x中运行良好(我想因为块arity没有强制执行).

然后升级到1.9.2 - 它仍然有效!这是一个奇怪的巧合,尽管lambda块参数是严格执行的(所以它会抱怨没有声明自己的参数),但是由于上面链接的bug - 实际上并没有在这个版本中传递self.

然后升级到1.9.3,修复了那个bug,所以它开始抛出参数错误 - 对于一个小版本改变恕我直言,这非常令人惊讶.

所以一个解决方法是声明参数,或者使lambda成为块:

 l = proc {  }
  myobj.instance_eval(&l) # fine
Run Code Online (Sandbox Code Playgroud)

只是想要描述完整的故事,以帮助他人避免像我一样浪费时间 - 直到这被正确记录.

Fra*_*oto 3

阅读Ruby的源代码,我可以解释的是:

instance_eval 正在执行此操作:

return specific_eval(argc, argv, klass, self)
Run Code Online (Sandbox Code Playgroud)

依次运行:

 if (rb_block_given_p()) {
     if (argc > 0) {
         rb_raise(rb_eArgError, "wrong number of arguments (%d for 0)", argc);
     }
     return yield_under(klass, self, Qundef);
 }
Run Code Online (Sandbox Code Playgroud)

您可以看到它们传递Qundef了 VALUES 参数。

if (values == Qundef) {
    return vm_yield_with_cref(th, 1, &self, cref);
}
Run Code Online (Sandbox Code Playgroud)

在该特定代码行中,他们手动将 argc(参数计数)设置为 1,并将参数设置为“self”。稍后,准备块的代码将块的参数设置为这些参数,因此第一个参数 =“self”,其余参数为零。

设置块参数的代码正在执行以下操作:

   arg0 = argv[0];

   ... bunch of code ...

     else {
         argv[0] = arg0;
     }

     for (i=argc; i<m; i++) {
         argv[i] = Qnil;
     }
Run Code Online (Sandbox Code Playgroud)

导致:

1.9.3p194 :006 > instance_eval do |x, y, z, a, b, c, d| x.class end
 => Object 
1.9.3p194 :008 > instance_eval do |x, y, z, a, b, c, d| y.class end
 => NilClass 
Run Code Online (Sandbox Code Playgroud)

为什么 ?我不知道,但代码似乎是故意的。很高兴向实施者提出问题,看看他们对此有何评论。

[编辑]

这可能是这样的,因为您传递给 instance_eval 的块可能会也可能不会为其制作(代码取决于将 self 设置为您想要块修改的类),相反,它们可能会假设您将向它们传递您希望它们作为参数进行修改的实例,这样它们也可以与 instance_eval 一起使用。

irb(main):001:0> blk = Proc.new do |x| x.class end
#<Proc:0x007fd2018447b8@(irb):1>
irb(main):002:0> blk.call
NilClass
irb(main):003:0> instance_eval &blk
Object
Run Code Online (Sandbox Code Playgroud)

当然这只是一个理论,没有官方文档我只能猜测。