8

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?

John Ledbetter
  • 13,557
  • 1
  • 61
  • 80
  • 1
    well it seems that in currying the function it binds the variables `test_func` to be whatever the local `test_func` is. I'm trying to think of why this should be and can't come up with anything, also playing around I can't find any way of getting the expected result (a correctly bound `test_func` on a curried proc). Nice question. – Mike H-R Jul 02 '14 at 14:53
  • 2
    Yea this is an interesting one. It looks like after currying the proc it binds the scope to main and despite evaluating it within within test it maintains scoping to main. – jvans Jul 02 '14 at 14:59
  • I reported this as a bug [here](https://bugs.ruby-lang.org/issues/10006), We'll see if it actually is or if someone there can explain. – John Ledbetter Jul 02 '14 at 18:35

1 Answers1

5

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!)

simonwo
  • 3,171
  • 2
  • 19
  • 28