My understanding of Ruby blocks and procs was that they are all closures. Now that I've seen it in use with instance_eval, I'm a little confused. What is the magic-sauce, the under workings when looking at the bare metal, that changes how a block's scope behaves under most common uses when compared to use with instance_eval?
Here is an example you can dump in IRB to see what I mean. I've included both a proc.call and block yield version example. Happily, they both behave the same way.
# Testing block/proc and eval
class Example
def initialize(value)
# value defined in the instance
@value = value
end
def call_a_proc(proc)
proc.call self
end
def yield_to_block
yield self
end
end
# Value defined in the global object
@value = 1
example = Example.new 'a'
# the block/proc that prints @value
proc1 = -> instance { puts @value }
# instance calling/yielding the block/proc that prints @value
proc2 = -> instance { instance.call_a_proc proc1 }
proc3 = -> instance { instance.yield_to_block &proc1 }
# instance_eval of the block that prints @value
proc4 = -> instance { instance.instance_eval &proc1 }
# the block/proc reference @value from the global context, the context in which it was defined (standard closure)
example.call_a_proc proc1
example.yield_to_block &proc1
example.call_a_proc proc2
example.yield_to_block &proc2
example.call_a_proc proc3
example.yield_to_block &proc3
# block/proc has it's context redefined as coming from within the instance.
example.call_a_proc proc4
example.yield_to_block &proc4
I understand that this is the point of the instance_eval method, I'm just not exactly sure how it works.
When you define @value
in the lexical scope (your main source file), you're defining an instance variable in the global interpreter. For example:
self #=> main
# "self" here refers to the main interpreter, which is of class Object
self.instance_variable_get(:@value) #=> 1
# "example" is your instance above
example.instance_variable_get(:@value) #=> "a"
# "self" has been changed to refer to "example" using instance_eval
example.instance_eval { self.instance_variable_get(:@value) } #=> "a"
# this syntax is just a shortcut for the line above
example.instance_eval { @value } #=> "a"
With instance_eval
, all you're doing is replacing the main interpreter self
with the object you've called instance_eval
on.