7

刚刚意识到instance_eval产生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 > 

这是在某处记录/规范的吗?查看ruby​​-doc:BasicObject,看不到任何提到的块参数。

当它总是被定义时,是否有理由——除了一些纯粹的历史原因——明确地传递它?


我被这个击中的方式是:

l = lambda {  }
myobj.instance_eval(&l)  # barks

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

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

然后升级到修复了该错误的 1.9.3,因此它开始抛出参数错误 - 对于一个较小的版本更改恕我直言,这非常令人惊讶。

因此,一种解决方法是声明参数,或者将 lambda 设置为块:

 l = proc {  }
  myobj.instance_eval(&l) # fine

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

4

3 回答 3

3

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

instance_eval 正在执行这个:

return specific_eval(argc, argv, klass, self)

依次运行:

 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);
 }

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

if (values == Qundef) {
    return vm_yield_with_cref(th, 1, &self, cref);
}

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

设置块参数的代码正在执行:

   arg0 = argv[0];

   ... bunch of code ...

     else {
         argv[0] = arg0;
     }

     for (i=argc; i<m; i++) {
         argv[i] = Qnil;
     }

导致:

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 

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

[编辑]

这可能是这样的,因为您传递给 instance_eval 的块可能是也可能不是为它制作的(取决于 self 的代码被设置为您希望该块修改的类),相反,他们可能会假设您将传递给他们instance 您希望它们作为参数进行修改,这样它们也可以与 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

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

于 2012-09-28T22:37:55.463 回答
1

我刚刚发现,与主要用于字符串评估的#instance_eval 不同,主要用于块评估的#instance_exec 没有所描述的行为:

o = Object.new
o.instance_exec { |*a| puts "a.size is #{a.size}" }
  => a.size is 0

这可能是一个意外的不一致,所以您可能已经发现了一个错误。将其发布在Ruby 错误上。

于 2012-09-28T22:50:37.280 回答
0

我刚刚在这里问了同样的问题:Ruby lambda's proc's and 'instance_eval'

在阅读了答案并完成了一些代码之后,我想我明白为什么 ruby​​ 有这种奇怪的(恕我直言)不一致。

它基本上允许Symbol#to_proc工作。

例如["foo", "bar"].each(&:puts)是缩写[...].each { |x| puts x }

不是

[...].each { self.puts }

所以 ruby​​ 也将 self 作为第一个参数传递给 proc,所以基本上 proc 可以使用 self 或其第一个参数。

由于实例 eval没有根据定义显式传递参数,这几乎总是不可见的行为。

proc 是 lambda 时例外。这不起作用:

2.4.1 :015 > foo = -> { puts 'hi' }
 => #<Proc:0x007fcb578ece78@(irb):15 (lambda)> 
2.4.1 :016 > [1, 2, 3].each(&foo)
ArgumentError: wrong number of arguments (given 1, expected 0)
    from (irb):15:in `block in irb_binding'
    from (irb):16:in `each'
    from (irb):16

所以我认为这成为问题的唯一一次是 instance_eval 与某个未知值一起使用时,您不知道 proc 是否为 lambda。在这种情况下,您必须这样做:

proc_var.lambda? ? instance_exec(&proc_var) : instance_eval(&proc_var)

奇怪(对我来说)红宝石并没有为你做这件事。

但我想你可以这样做:

alias original_instance_eval instance_eval 
def instance_eval(*args, &block)
  block&.lambda? ? instance_exec(&block) : original_instance_eval(*args, &block)
end
于 2018-10-04T10:02:48.630 回答