4

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.

1 Answer 1

5

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.

Sign up to request clarification or add additional context in comments.

3 Comments

The Binding class, perfect! I'll dig more into this magic when I have a spare weekend, hahah. Thanks!
No, instance_eval doesn't change the Binding of the block. Quite the opposite, actually: the local variable bindings are pretty much the only thing it doesn't change. What it changes, is the value of self.
Yes! Sorry for the error, and thanks for the comment. Just cracked the metaprogramming ruby book again and will update the answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.