Norswap
Norswap

Reputation: 12222

Ruby - define_method and closures

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´ ?

Upvotes: 3

Views: 1429

Answers (3)

Niklas B.
Niklas B.

Reputation: 95318

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.

Upvotes: 5

Sony Santos
Sony Santos

Reputation: 5545

Inspired on the comment of Niklas B.:

class TestClass
  def exec_block(&block) ; yield ; end
end
s = self

TestClass.new.send(:exec_block) do ; puts s ; end
# -> main

TestClass.send(:define_method, :bing) do ; puts s ; end
TestClass.new.bing
# -> main

Upvotes: 0

luacassus
luacassus

Reputation: 6720

First thing - when you passing a block explicitly to the method instead of yield you can use block.call. Second thing - inside exec_block method replace yield with instance_eval(&block) and you will see the magic ;)

Little bit more clarification - in the first example the block catches the local scope along with self variable which is pointing to main object.

In the second example (with define_method) the block will be treated as a new method body and it will be evaluated inside object's scope using instance_eval. For more details you can check: http://apidock.com/ruby/Module/define_method

Upvotes: 0

Related Questions