Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

instance_eval's block argument(s)- documented? purpose?

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.

like image 610
inger Avatar asked Sep 28 '12 22:09

inger


2 Answers

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.

like image 84
Francisco Soto Avatar answered Oct 13 '22 12:10

Francisco Soto


I have just dicovered that unlike #instance_eval, which is primarily intended for string evaluation, #instance_exec primarily intended for block evaluation, does not have the described behavior:

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

This is probably an unintended inconsistency, so you might have discovered a bug. Post it on Ruby bugs.

like image 40
Boris Stitnicky Avatar answered Oct 13 '22 10:10

Boris Stitnicky