You can think of blocks as closing over both the set of local variables and the current self
.
In Ruby, you will always have access to local variables, no matter what. The self
encapsulates instance methods on the current object as well as instance variables.
Consider the following code:
class Table
def initialize(legs)
@legs = legs
end
def with_legs
yield @legs
end
end
And then:
def some_calling_method
name = "Yehuda"
Table.new(4) {|legs| puts "#{name} gnaws off one of the #{legs} legs" }
end
By Ruby's block semantics, you can be assured that name
will be available inside the block, even without looking at the method you're calling.
However, consider the following:
class Person
def initialize(name)
@name = name
end
def gnaw
Table.new(4).with_legs do |legs|
puts "#{@name} gnaws off one of the #{legs} legs"
end
end
end
Person.new("Yehuda").gnaw
In this case, we are accessing the @name
instance variable from inside the block. It works great in this case, but is not guaranteed. What if we implemented table a bit differently:
class Table
def initialize(legs)
@legs = legs
end
def with_legs(&block)
self.instance_eval(&block)
end
end
Effectively, what we're saying is "evaluate the block in the context of a different self." In this case, we are evaluating the block in the context of the table. Why would you do that?
class Leg
attr_accessor :number
def initialize(number)
@number = number
end
end
class Table
def initialize(legs)
@legs = legs
end
def with_leg(&block)
Leg.new(rand(@legs).instance_eval(&block)
end
end
Now, you could do:
class Person
def initialize(name)
@name = name
end
def gnaw
Table.new(4).with_leg do
puts "I'm gnawing off one of leg #{number}"
end
end
end
If you wanted access to the person object inside of the block, you'd have to do:
class Person
def initialize(name)
@name = name
end
def gnaw
my_name = name
Table.new(4).with_leg do
puts "#{my_name} is gnawing off one of leg #{number}"
end
end
end
As you can see, the use of instance_eval can make it simpler and less bulky to access methods of a far-off object inside a block, but comes at the cost of making the self
unaccessible. The technique is usually used in DSLs, where a number of methods are injected into the block, but the self doesn't matter that much.
This is what's happening with Tk; they're using instance_eval to inject their own self
into the block, which is wiping your self
clean.