Ruby - define_method and closures
closures, reflection, ruby, self
Solution
`self` is captured by the closure like any other variable. We can verify that by passing a `Proc` around different object instances:
class A
def exec_block(&block)
block.call
end
end
class B
def exec_indirect(&block)
A.new.exec_block(&block)
end
end
block = proc { p self }
a = A.new; b = B.new
a.exec_block(&block) # => main
b.exec_indirect(&block) # => main
However, `BasicObject#instance_eval` and the alikes rebind the `self` variable dynamically:
In order to set the context, the variable `self` is set to obj while the code is executing, giving the code access to obj’s instance variables
`Module#define_method` in turn uses `instance_eval` to execute the associated block:
If a block is specified, it is used as the method body. This block is evaluated using instance_eval [...]
Observe:
A.send(:define_method, :foo, &block)
a.foo # => #<A:0x00000001717040>
a.instance_eval(&block) # => #<A:0x00000001717040>
With that knowledge, you can now rewrite your `exec_block` to use `instance_eval`:
class A
def exec_block(&block)
instance_eval(&block)
end
end
block = proc { p self }
A.new.exec_block(&block) # => #<A:0x00000001bb9828>
As mentioned before, using `instance_eval` seems to be the only way to run a `Proc` instance with a modified context. It can be used to implement dynamic binding in Ruby.
Problem
define_method exhibits the following behaviour: ``` class TestClass def exec_block(&block) ; yield ; end end TestClass.new.send(:exec_block) do ; puts self ; end # -> main TestClass.send(:define_method, :bing) do ; puts self ; end TestClass.new.bing # -> <TestClass:...> ``` What I do not understand is that the the block passed to define_method is supposed to be a closure. As such it should (at least according to my understanding) capture the value of `self` as `main`, as exhibited when calling `exec_block`. I understand that the block will become the body of the method, yet I do not understand the reason for the behavior. Why does the block evaluate to different things when used with different methods ? How can I reproduce the behavior of block with `define_method` for other methods ? i.e. how could I write `exec_block` to have it output `<TestClass:...>` instead of `main´ ?