Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to instance_eval a curried proc?

Tags:

ruby

currying

Suppose I have a class such as this:

class Test
  def test_func
    140
  end
end

And a proc, which references a member function from Test:

p = ->(x, y) { x + y + test_func }  # => #<Proc:0x007fb3143e7f78@(pry):6 (lambda)>

To call p, I bind it to an instance of Test:

test = Test.new                     # => #<Test:0x007fb3143c5a68>
test.instance_exec(1, 2, &p)        # => 143

Now suppose I want to pass just y to p, and always pass x = 1:

curried = p.curry[1]                # => #<Proc:0x007fb3142be070 (lambda)>

Ideally I should be able to just instance_exec as before, but instead:

test.instance_exec(2, &curried)

=> NameError: undefined local variable or method `test_func' for main:Object

The proc runs in what seems to be the incorrect binding. What gives?

like image 811
John Ledbetter Avatar asked Jul 02 '14 14:07

John Ledbetter


1 Answers

Yes, I believe this is a bug.

I think it comes down to the fact that curry returns a "C level proc" rather than a normal proc. I don't fully understand the difference between the two (I'm guessing the former is one created by the Ruby C code which is what curry does), but you can tell they're different when you try and take a binding.

p.binding # => #<Binding:0x000000020b4238>
curried.binding # => ArgumentError: Can't create a binding from C level Proc

By looking at the source, this looks like their internal struct representations have different values for the iseq member, which says what kind of instruction sequence this block holds.

This is significant when you call instance_exec, which eventually ends up calling invoke_block_from_c in vm.c, which branches depending on the iseq type:

else if (BUILTIN_TYPE(block->iseq) != T_NODE) {
    ...
} else {
    return vm_yield_with_cfunc(th, block, self, argc, argv, blockptr);
}

The branch I missed out (...) ends up calling vm_push_frame with what looks like some environment where as vm_yield_with_cfunc doesn't.

So my guess would be that because the curried proc is created in C code and ends up of a different 'type' than your first proc, the other branch is taken in the above snippet and the enviornment isn't used.

I should point out that all of this is pretty speculative based on reading the code, I haven't run any tests or tried anything out (and I'm also not all that familiar with internal Ruby anyway!)

like image 86
simonwo Avatar answered Oct 22 '22 21:10

simonwo