问题描述
刚刚意识到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
只是想描述整个故事,以帮助其他人避免浪费时间,直到我妥善记录为止.
阅读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);
}
您可以看到它们将VALUES参数传递给Qundef
.
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的块可能是(也可能不是)特制的(代码取决于自己是否被设置为您要修改块的类),相反,它们可能会假设您要将您希望他们修改的实例作为参数传递给他们,这样,它们也可以与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
当然,这只是一个理论,没有官方文件,我只能猜测.
Just realized that instance_eval
yields self
as an argument to the associated block (except for a bug in the 1.9.2 version: 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 >
Is this documented/spec'ed somewhere? Looking at ruby-doc:BasicObject, can't see any block params mentioned.
Is there a reason -apart from some purely historical one- for passing it explicitly when it self is always defined anyway?
The way I was hit by this is:
l = lambda { }
myobj.instance_eval(&l) # barks
This worked fine in 1.8.x (I guess because of block arity wasn't enforced).
Then upgraded to 1.9.2 - and it still worked! That's a strange coincidence as even though lambda block arguments are strictly enforced (so it would have complained for not declaring the argument for self), however due to the bug linked above - the self actually wasn't passed in this version..
Then upgraded to 1.9.3 where that bug got fixed, so it started to throwing the argument error - pretty surprising for a minor version change IMHO.
So one workaround is do declare parameter, or make lambda a block instead:
l = proc { }
myobj.instance_eval(&l) # fine
Just thought to describe the full story to help others avoid wasting time the way I did - until this is properly documented.
Reading Ruby's source code, what I can interpret is:
instance_eval is executing this:
return specific_eval(argc, argv, klass, self)
which in turn runs:
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);
}
You can see they pass Qundef
for the VALUES argument.
if (values == Qundef) {
return vm_yield_with_cref(th, 1, &self, cref);
}
In that particular line of code, they set manually argc (argument count) to 1 and the argument as "self". Later on the code that prepares the block sets the arguments to the block to these arguments, hence the first argument = "self" and the rest are nil.
The code that sets up the block arguments is doing :
arg0 = argv[0];
... bunch of code ...
else {
argv[0] = arg0;
}
for (i=argc; i<m; i++) {
argv[i] = Qnil;
}
Resulting in:
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
Why ? I have no idea but the code seems to be intentional. Would be nice to ask the question to the implementers and see what they have to say about it.
[Edit]
This probably is like that because the blocks you pass to instance_eval may or may not be crafted for it (code that depends on self being set to the class you want the block to modify), instead they may assume you are going to pass them the instance you want them to modify as an argument and in this way they would work with instance_eval as well.
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
Of course this is only a theory and without official documentation I can only guess.
这篇关于instance_eval的块参数-已记录?目的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!