Ruby Ruby 作用域问题,应该如何访问域外的实例方法

dfzy5566 · 2017年07月21日 · 最后由 dfzy5566 回复于 2017年07月23日 · 2900 次阅读

提这个问题之前,我去 https://gitter.im/rails/rails 也发表过,不过没有得到解决方案。 直接上代码,现在在类A中定义了跨作用域的类B,我希望可以在B中访问到A的实例方法hello. 尝试了很久,没有实现 - -求大神指导一下。

class A
  attr_accessor :name

  def hello
    "Hello #{name}"
  end

  B = Class.new do
    define_singleton_method :hi do
      hello
    end
  end

end

class C < A
  def say_hi
    B.hi
  end
end


obj = C.new(:name => 'John')
obj.say_hi #=> 希望得到 Hello John

为什么要这样写呢?似乎不太容易,也不规范啊。

问题是B.new只是创建了一个namenilclass,所以并不继承自A,父类是Object,所以你这样写B.hello是还没有定义的,如果定义def hello那么可以用A::B.new.hello来调用 但因为B并不继承A,所以A::B:hello方法也不能super 我看了下p self.methodsp A::B.methods没找到怎么声明继承 不会这种情况声明继承

class A
  attr_accessor :name
  def hello
    puts "Hello #{name}"
  end
  B = Class.new do
    p self.methods
    p self.name #=> nil
    p self.class #=> class
    define_singleton_method :hi do
      def hello
         p 'hello1'
         #super 
      end
    end
  end
  p B.class #=> class
end
class C < A
  def say_hi
    B.hi
  end
end

p A::B.superclass #=> Object
obj = C.new
obj.name='John'
obj.say_hi 
a=A::B.new
p a.methods
a.hello #=> "hello1"
p A::B.methods
zaqmjuop 回复

恩 分析的有道理,但是还是有几点疑问。 1 我也没有说过 B 继承 A 呀😂 2 你的 B.new.name 为空,我猜测这个 name 不是 A 的 name,而是 class 的 name,因为这个时候 block 还没执行完,所以为 nil 3 如果你把 hello 改成 self.hello 再试试😊

dfzy5566 回复

B 是一个 class 又不继承 A 那 B 里面调用的 hello 是从哪里来的。name 的值不应该是到命令行查看才知道的么,为什么还要猜测。self.hello 是一个 class.hello 所以它当然调用 class 内方法 而这个 class 里并没有声明 hello 方法 它又不继承自 A 无法 super 他只是 A 中一个 class.new 实例并不是 A.new,它凭什么可以调用 A 的 hello 方法呢

LZ 似乎没搞懂运行时 self 的指向、类与实例的区别、继承链,这三个问题

Rei 回复

不能这么说,因为无法没问题,我也知道这代码执行有问题,所以希望又说来分析下原因究竟出在哪里。至于代码出于什么目的,只能说没有目的,凭空想的。

dfzy5566 回复

无法=语法

dfzy5566 回复

你提问都说了“跨作用域”,跨作用域当然是不能访问拉。

zaqmjuop 回复

事实是 B 不能调用 hello,这也是我提问的原因,我想知道为什么,如果 hello 是一个变量,比如字符串,这个时候 B 却又可以调用 hello,这就是让人很困惑的事。

另外,和解决问题无关的批判是无意义的。

Rei 回复

哈哈 可能描述有问题,但是代码你也看到了,确实打破了作用域的。

dfzy5566 回复

奥,你定义了自己的 Ruby 语法。这个语法在 MRI 里不能执行,也许自己写一个解释器可以。

Rei 回复

不能执行不见得是语法问题呀,其实我想知道的是为什么不能执行,和如何才能像预期那样执行,这也是提这个问题的初衷。

你的问题可以简化为:

class A
  def a_method
    "call from a"
  end
end

class B
  def b_method
    a_method
  end
end

B 定义在 A 里面和外面对于实例方法并没有什么不同——实例方法需要通过实例调用。

想要用 B 调用 A 的方法,那么 new 一个实例:

class B
  def b_method
    A.new.a_method
  end
end
Rei 回复

够清晰,谢谢解答!

dfzy5566 回复

你在 B 是在 A 里的 class.new 他和 A 并没有任何关系

zaqmjuop 回复

我觉得还是有个命名空间的关系

class A # 新的作用域
  attr_accessor :name

  def hello # 新的作用域
    "Hello #{name}"
  end

  B = Class.new do # 没有进入新的作用域
    define_singleton_method :hi do
      hello # self是B,但是B中没有定义hello
    end
  end

end

其实你这个问题在《ruby 元编程》里有答案

laplace 回复

嗯嗯 但是如果 hello 是个字符串的时候却又可以访问到,奇怪

21 楼 已删除
dfzy5566 回复

实际上,你搞混了一个东西。当 hello 是 字符串与 def hello; end 是完全两个不同的东西。 我认为你指的 hello 是这个意思。

class A
  hello = "test"

  def hello_again
  end

  B = Class.new do
    define_singleton_method :hi do
      hello
    end
  end

end

class C < A
  def say_hi
    B.hi
  end
end

基于这个假设。我讲一下为什么字符串hello 可以被访问到。

因为解释器在读取这段代码时,当读到define_singleton_method ,此时do block 是可以访问当前的作用域的。 那么当前的作用域到底是什么?并不是class A内部的作用域,而是,正在定义class A的作用域,也可以理解为 Class.new do ; end 这个作用域。 本来,如果你没有在 B.hi 中使用 hello 的话,这个hello 在加载完这段代码后 hello 这个变量就释放了(有可能有理解的错误),因为除了在定义 A 的代码里用了一下,其他地方就再也没有绑定过这个变量了,但是你定义了 B.hi ,把这个变量又和 B.hi 绑定到一起了。所以 hello 这个变量没有被释放掉。

上面解释了为什么字符串hello可以被访问。

上面的这些东西在The Ruby Programming Language 这本书里有讲到过。可以看一下。

之前没看你后面的评论。发现你说 hello 是变量的问题,你说“很奇怪”。其实一点也不奇怪,只因为你没搞懂作用域当前类self闭包这些概念。

你用 Class.new 在 A 中创建一个类,这时并没有打开新的作用域,所以那个hi方法可以访问相同上下文中的hello 变量。而hello 方法的调用,要通过一个叫方法查找的过程,这个过程重度依赖self,你在 B 类中直接调用hello方法,默认self就是 B Class,而 B 中没有hello方法,所以你在hi中调用hello会报NoMethodError

还有就是 A 中的hello方法是个实例方法,实例方法要通过一个类的实例来调用,直接用没有接收者的方式调用是不行的,语法错误。

劝你多读书

==================================

再补充一点,你还要研究一下 ruby 的对象模型,看看对象实例变量实例方法这些内容的关系,你会发现实例变量跟实例方法保存在不同的地方,然后你的问题就豁然开朗了

laplace 回复

谢谢您的教诲,回头我有时间去读一下 24 史,资治通鉴。

tesla_lee 回复

谢谢你的耐心解答。其实我之所以提这个问题就是因为肯定没有完全清楚的地方嘛,当然其中也不乏各种测试,得到的结果没有说得过去的理论支持。所以跑过去求大神解惑。您刚说def hellostr hello是不一样的,我觉得可能还有种特殊情况说不过去,麻烦看一下,在 irb 中,不在 A 类中的情况:

$ irb
irb(main):001:0> def hello
irb(main):002:1>   "Hello world"
irb(main):003:1> end
=> :hello
irb(main):004:0> B = Class.new do
irb(main):005:1*     define_singleton_method :hi do
irb(main):006:2*       hello
irb(main):007:2>     end
irb(main):008:1>   end
=> B
irb(main):009:0> B.hi
=> "Hello world"
dfzy5566 回复

说得过去的,思路和我上面说的一样,你思考一个问题,在 irb 中,当前的作用域是什么?define_singleton_methon 时,这个 do block 可以访问的作用域是什么?我能告诉你的是,和上面的情况是一样的,他们在同一个作用域。基本上@laplace已经指出了问题了。文字比较生硬,可以忽略大家的语气。不用在意。

tesla_lee 回复

嗯,理解了,因为 main 是 Object,所以方法会被查找到,并不是因为打破作用域的效果,Thanks

需要 登录 后方可回复, 如果你还没有账号请 注册新账号