【问题标题】:Ruby - Lexical scope vs InheritanceRuby - 词法范围与继承
【发布时间】:2013-02-13 17:03:20
【问题描述】:

这是原始 SO 问题的延续:Using "::" instead of "module ..." for Ruby namespacing

在最初的 SO 问题中,这是我仍然无法理解的场景:

FOO = 123

module Foo
  FOO = 555
end

module Foo
  class Bar
    def baz
      puts FOO
    end
  end
end

class Foo::Bar
  def glorf
    puts FOO
  end
end

puts Foo::Bar.new.baz    # -> 555
puts Foo::Bar.new.glorf  # -> 123

有人可以解释一下为什么第一个调用返回 555 以及为什么第二个调用返回 123?

【问题讨论】:

  • Willson,您认为下面哪个答案值得赏金?谢谢
  • 提示:在代码中的两个 put 之后添加“puts Module.nesting”。另请参阅:coderrr.wordpress.com/2008/03/11/…

标签: ruby inheritance lexical-scope


【解决方案1】:

您可以将module Somethingclass Somethingdef something 的每次出现视为进入新范围的“门户”。当 Ruby 搜索已被引用的名称的定义时,它首先在当前范围(方法、类或模块)中查找,如果没有找到,它将返回每个包含“网关”并搜索那里的范围。

在您的示例中,方法 baz 定义为

module Foo
  class Bar
    def baz
      puts FOO
    end
  end
end

因此,当尝试确定FOO 的值时,首先检查Bar 类,由于Bar 不包含FOO,因此搜索向上移动通过“class Bar 网关”进入Foo 模块,它是包含范围。 Foo 确实包含一个常量 FOO (555) 所以这是你看到的结果。

方法glorf定义为:

class Foo::Bar
  def glorf
    puts FOO
  end
end

这里的“网关”是class Foo::Bar,所以当FOO 不在Bar 中时,“网关”会通过Foo 模块直接进入顶层,那里还有另一个@987654340 @ (123) 这是显示的内容。

注意使用class Foo::Bar 如何创建一个“网关”,跳过Foo 的范围,但module Foo; class Bar ... 会打开两个单独的“网关”

【讨论】:

  • 顺便说一句。网关术语。在 Ruby 源代码中,似乎有一个可以称为“范围堆栈”的东西。因此,每次您键入 classmodule 时,都会将一个新作用域推入此堆栈。当 Ruby 然后搜索变量或常量时,它会从下到上查询此堆栈,如果在向上的过程中没有找到变量,则以顶层 main 结尾。对于class Foo::Bar,它确实应该将两个作用域推入堆栈(FooBar),但它只推一个,因此我们遇到了“问题”。
  • 这与原始答案有何不同?
  • @Casper 说得通。我在某处读到了“网关”的想法(不幸的是不记得在哪里)作为思考正在发生的事情的一种方式,但我还没有查看实现。我想对这种行为的一种解释是它允许您打开一个嵌套类(对其进行修补),而无需担心封闭范围的干扰。
【解决方案2】:

哇,好问题。我能想到的最佳答案是在这种情况下,您使用模块来定义命名空间。

看看这个:

FOO = 123

module Foo
  FOO = 555
end

module Foo
  class Bar
    def baz
      puts FOO
    end

    def glorf3
      puts ::FOO
    end
  end
end

class Foo::Bar
  def glorf2
    puts Foo::FOO
  end

  def glorf
    puts FOO
  end
end

puts Foo::Bar.new.baz    # -> 555
puts Foo::Bar.new.glorf  # -> 123
puts Foo::Bar.new.glorf2  # -> 555
puts Foo::Bar.new.glorf3  # -> 123

所以我的想法是,当你定义:

module Foo
  FOO = 555
end

您正在Foo 的命名空间中创建FOO。所以当你在这里使用它时:

module Foo
  class Bar
    def baz
      puts FOO
    end
  end
end

您位于Foo 命名空间中。但是,当您在以下位置引用它时:

class Foo::Bar
  def glorf
    puts FOO
  end
end

FOO 来自默认命名空间(如 ::FOO 所示)。

【讨论】:

  • 感谢您提供的示例!这是有道理的,除了一点:当你定义类 Foo::Bar 时,你不是将它命名为 Foo 吗? “Foo::Bar”的“Foo::”部分不是暗示您正在为该类命名空间吗?
  • Foo::Bar.new.glorf 返回 123 让我难以理解 Foo::Bar.new.baz 返回 555 时。
  • 我也想过,但看起来,发生的事情是你正在通过 Foo::Bar 显式地命名 Bar(在 Foo 下),并且该上下文中的所有其他内容仍然来自默认值命名空间。
  • 很有趣,所以当你显式命名类时,常量将解析为默认命名空间,除非你另外指定(即 Foo::FOO 在 glorf2 中)并且当你隐式命名空间类,常量将解析为命名空间,除非您另外指定(即:::FOO 在 glorf3 中)。
  • 对,我就是这么想的。希望其他人发表评论。这真的很有趣......
【解决方案3】:

第一次通话:

puts Foo::Bar.new.baz    # -> 555

打印调用类 Foo::Bar

实例的方法 baz 的结果

请注意,Foo::Bar#baz 定义实际上是 FOO 的闭包。遵循 ruby​​ 的作用域规则:

  1. FOOFoo::Bar(类,而不是实例)范围内搜索,没有找到,
  2. FOO 在封闭范围 Foo 中搜索(因为我们在模块定义中)并在那里找到 (555)

第二次调用:

puts Foo::Bar.new.glorf  # -> 123

打印调用 Foo::Bar

类实例的方法 glorf 的结果

注意这次 Foo::Bar#glorf 定义也是 FOO 的闭包,但是如果我们遵循 ruby​​ 的作用域规则,您会注意到该值已关闭此时是 ::FOO(顶级范围 FOO),方式如下:

  1. FOOFoo::Bar(类,而非实例)命名空间中搜索,未找到
  2. FOO 在封闭范围('顶级')中搜索并在那里找到(123)

【讨论】:

    【解决方案4】:

    glorf 是 Foo 类的一个方法,在=> [Foo, Module, Object, Kernel, BasicObject]

    在该范围内(即在默认/主模块中),FOO 被分配 123

    模块 Foo 定义为

    module Foo
      FOO = 555
      class Bar
        def baz
          puts FOO
        end
      end
    end
    

    其中方法 baz 属于模块 Foo => [Bar, Foo, Object, Kernel, BasicObject] 中的类 Bar

    在那个范围内,FOO 被分配了 555

    【讨论】:

    • 上面提到的'main'是irb的一个artifact,实际上FOO=123是一个进入Object类的顶级方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-13
    • 1970-01-01
    • 2016-03-29
    • 1970-01-01
    • 1970-01-01
    • 2014-07-26
    • 2015-07-19
    相关资源
    最近更新 更多