This Ruby (2.2.3p173) code:
class A
def msg
"hello"
end
def p
Proc.new { msg }
end
def lam
lambda { msg }
end
def blk
(1..3).map { msg }.join(" and ")
end
def d1(obj)
obj.define_singleton_method(:say1) { msg }
end
def d2(obj)
bound = msg # <== Why is this needed?
obj.define_singleton_method(:say2) { bound }
end
end
a = A.new
puts a.p.call
puts a.lam.call
puts a.blk
obj = Object.new
a.d1(obj)
begin
# Why does this fail?
puts obj.say1
rescue
puts "caught: #{$!}"
end
a.d2(obj)
puts obj.say2
produces this output:
hello
hello
hello and hello and hello
caught: undefined local variable or method `msg' for #<Object:0x00000001a20638>
hello
What makes the difference in d1 and d2? Why do all of the blocks see msg except for the one passed to define_singleton_method?
Update:
I think it comes down to this:
Proc bodies have a lexical scope like functions in Lisp and JavaScript, meaning that when Ruby encounters a free variable inside a proc body, its value is resolved within the context the proc was defined. This is what makes closures possible. Methods are different, however. A method's context is the object to which they are bound. When Ruby encounters a free variable inside a method body, it assumes the variable refers to another method on the object, and this is what saves us from having to prefix same-object methods with "this" or "self".
which I found here: Of closures, methods, procs, scope, and Ruby.
All of these various blocks with { msg } must work as Proc body does. define_singleton_method is taking the block and giving it method's rules.
The answer has to do with self and scopes
There are three ways to create a new scope in ruby. Classes, Modules, and Methods.
Your class creates a scope, and each of your methods create a scope that contain bindings specific to them. Closures are special though. A closure will grab the bindings that are around when you define the block and the block specific bindings disappear after the block ends. For example:
def my_method
#Method scope
x = "Goodbye"
yield("Cruel")
end
x = "Hello"
#Closure says "I am going to grab the local bindings from my scope
my_method {|y| "#{x}, #{y} world" }
When ever you write the code
obj.define_singleton_method(:say1) { msg }
The only local bindings the closure grabs is 'obj' This can be demonstrated by modifying the code like so:
def d2(obj)
puts "in the scope of method :d2, I have acces to the :msg method: #{methods.include?(:msg)}"
puts "---"
obj.define_singleton_method(:say2) do
puts "in the scope of this closure, I have acces to the :msg method: #{methods.include?(:msg)}"
puts "Things I do have access to: "
puts methods
puts local_variables
end
end
A simple print statement of the most important part of ruby, self, will show you that you are operating in different scopes. Check out the code below:
def d2(obj)
puts "in the scope of method :d2, I am operating as #{self}"
puts "---"
obj.define_singleton_method(:say2) do
puts "in the scope of this closure, I am operating as #{self}"
end
end
So in short, the reason is because of scope. Whenever you declare bound = msg you are making the contents of msg local to the method and then the closure then can pick up the local binding value of msg.
If you want to read more about how this works, I highly recommend "The Pragmatic Programmers - Metaprogramming Ruby" You will learn a lot about self, and closures. http://www.amazon.com/Metaprogramming-Ruby-Program-Like-Facets/dp/1941222129
----EDIT---- "Why is"
def p
Proc.new { msg }
end
different than
def d2(obj)
obj.define_singleton_method(:say2) { msg }
end
It is different because self inside the block is different. Inside the method definition "p", the block has access to instance variables and methods, whereas the method "d2" has a block that only has access to Object. We can prove this with a little monkeypatching. Add this code:
class Object
def msg
"GoodBye"
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With