Ruby 实例评估';s块参数-记录?目的?
刚刚意识到,Ruby 实例评估';s块参数-记录?目的?,ruby,metaprogramming,Ruby,Metaprogramming,刚刚意识到,instance\u eval产生self作为相关块的参数(1.9.2版本中的bug除外:) 这在1.8.x中运行得很好(我想是因为没有强制执行块算术) 然后升级到1.9.2-它仍然有效!这是一个奇怪的巧合,尽管lambda块参数是严格执行的(因此它会抱怨没有为self声明参数),但是由于上面链接的错误,self实际上没有在这个版本中传递 然后升级到1.9.3,在那里这个错误得到了修复,所以它开始抛出参数错误-对于一个微小的版本更改IMHO来说非常令人惊讶 因此,一种解决方法是声明参
instance\u eval
产生self
作为相关块的参数(1.9.2版本中的bug除外:)
这在1.8.x中运行得很好(我想是因为没有强制执行块算术)
然后升级到1.9.2-它仍然有效!这是一个奇怪的巧合,尽管lambda块参数是严格执行的(因此它会抱怨没有为self声明参数),但是由于上面链接的错误,self实际上没有在这个版本中传递
然后升级到1.9.3,在那里这个错误得到了修复,所以它开始抛出参数错误-对于一个微小的版本更改IMHO来说非常令人惊讶
因此,一种解决方法是声明参数,或者将lambda改为块:
l = proc { }
myobj.instance_eval(&l) # fine
只是想描述整个故事,以帮助其他人避免像我这样浪费时间——直到这是正确的记录 阅读Ruby的源代码,我能解释的是: 实例_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);
}
您可以看到它们为VALUES参数传递Qundef
if (values == Qundef) {
return vm_yield_with_cref(th, 1, &self, cref);
}
在特定的代码行中,他们手动将argc(参数计数)设置为1,并将参数设置为“self”。稍后,准备块的代码将块的参数设置为这些参数,因此第一个参数=“self”,其余为零
设置块参数的代码正在执行以下操作:
arg0 = argv[0];
... bunch of code ...
else {
argv[0] = arg0;
}
for (i=argc; i<m; i++) {
argv[i] = Qnil;
}
为什么??我不知道,但代码似乎是故意的。最好向实现者提问,看看他们对此有何看法
[编辑]
这可能是这样的,因为您传递给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
irb(main):001:0>blk=Proc.new do | x | x.class结束
#
irb(主):002:0>blk.call
尼尔Class
irb(主):003:0>实例评估和blk
对象
当然,这只是一个理论,没有官方文档,我只能猜测。我刚刚发现,与主要用于字符串计算的#instance _eval不同,主要用于块计算的#instance _exec没有描述的行为:
o = Object.new
o.instance_exec { |*a| puts "a.size is #{a.size}" }
=> a.size is 0
这可能是意外的不一致,因此您可能发现了一个bug。发上去。我刚才在这里问了同样的问题: 在阅读了答案和一些代码之后,我想我理解了ruby为什么会有这种奇怪的(IMHO)不一致性 它基本上允许
Symbol#to_proc
工作
例如[“foo”,“bar”]。每个(&:put)
都是[…]的缩写。每个{x | put x}
不是
[…]每个{self.put}
所以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
但是我们仍然想知道这是否在某个地方被记录下来,self也被作为一个参数传递到里面。instance_eval的主要作用是设置self=receiver_object,不是吗?如果传递同一个对象(上面的[0]),那么它将始终是“self”,不是吗?所以这对我来说是多余的,我是否遗漏了什么?或者你是否将这个问题误解为“实例评估的要点是什么”。不,你不是。事实上,对象在块中始终作为self可用,不需要将其作为参数传入。我不认为我们遗漏了什么,这是一个错误。请投票支持Francisco的分析。你答案的第二个版本肯定很有帮助。除了给
instance_exec
的块所提供的参数是。这里的重点不是有记录的instance_exec行为,而是讨论有无无无无记录的行为。另外,减少对其他方法的假设,特别是不要假设我在写我的答案之前没有阅读完整的可用文档。为什么一种方法的文档和行为应该被视为另一种方法的文档?仅仅因为instance\u exec
以一种方式工作并不意味着instance\u eval
也应该这样做,当然也不会让它成为一个bug。它们毕竟是不同的方法。我同意两种不同的方法不同的行为不是错误本身(尽管我同意这可能被视为不一致,可能违反了POL)。真正的bug IMHO是未记录的行为,它实际上破坏了某些用例,然后在调试上浪费时间(除非您确实想查看源代码)。
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
o = Object.new
o.instance_exec { |*a| puts "a.size is #{a.size}" }
=> a.size is 0
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
alias original_instance_eval instance_eval
def instance_eval(*args, &block)
block&.lambda? ? instance_exec(&block) : original_instance_eval(*args, &block)
end