Ruby 的define_singleton_method 的闭包是如何工作的?

Jim*_*ood 5 ruby closures

这个 Ruby (2.2.3p173) 代码:

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
Run Code Online (Sandbox Code Playgroud)

产生这个输出:

hello
hello
hello and hello and hello
caught: undefined local variable or method `msg' for #<Object:0x00000001a20638>
hello
Run Code Online (Sandbox Code Playgroud)

d1 和 d2 有何不同?为什么除了传递给define_singleton_method的块之外,所有块都看到msg?

更新:

我认为归结为:

Proc 主体具有与 Lisp 和 JavaScript 中的函数类似的词法作用域,这意味着当 Ruby 在 proc 主体中遇到自由变量时,它的值将在 proc 定义的上下文中解析。这就是使闭包成为可能的原因。然而,方法是不同的。方法的上下文是它们绑定到的对象。当 Ruby 在方法体内遇到自由变量时,它假定该变量引用对象上的另一个方法,这使我们不必在相同对象的方法前加上“this”或“self”前缀。

我在这里找到的:关于闭包、方法、过程、范围和 Ruby

所有这些不同的块都{ msg }必须像 Proc 主体一样工作。define_singleton_method正在获取块并给它方法的规则。

Sch*_*lar 1

self答案与范围有关

在 ruby​​ 中创建新作用域的方法有3种。类、模块和方法。

您的类创建一个作用域,并且每个方法都创建一个包含特定于它们的绑定的作用域。不过,关闭很特殊。闭包将抓取定义块时周围的绑定,并且块特定的绑定在块结束后消失。例如:

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" }
Run Code Online (Sandbox Code Playgroud)

当你写代码的时候

obj.define_singleton_method(:say1) { msg }
Run Code Online (Sandbox Code Playgroud)

闭包获取的唯一本地绑定是“obj”,这可以通过修改代码来演示,如下所示:

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
Run Code Online (Sandbox Code Playgroud)

ruby 最重要的部分 self 的简单打印语句将向您展示您正在不同的范围内进行操作。查看下面的代码:

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
Run Code Online (Sandbox Code Playgroud)

简而言之,原因是范围。每当你声明bound = msg你正在将内容设置msg为方法的本地内容,然后闭包就可以获取本地绑定值msg

如果您想了解更多有关其工作原理的信息,我强烈推荐“The Pragmatic Programmers - Metaprogramming Ruby”,您将学到很多关于 self 和闭包的知识。 http://www.amazon.com/Metaprogramming-Ruby-Program-Like-Facets/dp/1941222129

----编辑----“为什么是”

def p Proc.new { msg } end

不同于

def d2(obj) obj.define_singleton_method(:say2) { msg } end

它是不同的,因为块内的 self 是不同的。在方法定义“p”内,该块可以访问实例变量和方法,而方法“d2”有一个只能访问对象的块。我们可以通过一点猴子补丁来证明这一点。添加此代码:

class Object def msg "GoodBye" end