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