关于全局/范围的Ruby方法查找

时间:2016-09-09 09:42:27

标签: ruby methods

我正在努力全面了解Ruby如何定位方法/符号,但是当涉及多个级别时,尤其是全局/文件范围时,我正在苦苦挣扎。

在类上显式调用方法时,会在顺序上有很多插图,其中包含它们所包含的类和模块(因此在每种情况下都是super调用的内容)。但是当没有明确地调用方法时,例如一个简单的func args而不是self.func args什么是搜索顺序?

为什么在下面的示例中,调用func的成员方法在全局之前找到成员方法,但func2找到没有调用method_missing的全局? 当全局变量是模块/类/类型时,为什么根本找不到具有相同名称的成员?

在方法中遇到“funcfunc()func arg”等时,是否有任何官方文档确切说明该语言的作用?有很多第三方博客,但他们只是真正谈论了include Moduleclass Type < BaseType的单个实例。

def func; "global func" end
def func2; "global func 2" end

class Test
  def x; func end
  def y; func2 end
  def z; Math end
  def w
    func = "local_var"
    [func(), func]
  end


  def func(arg=nil); "method func" end
  def func=(x); puts "assign func=" end
  def Math; "method Math" end

  def method_missing(sym, *args, &block)
    puts "method_missing #{sym}"
    super(sym, *args, &block)
  end
end

x = Test.new
puts x.x.inspect # "method func", member overrides global
puts x.y.inspect # "global func 2", "method_missing" was not called
puts x.z.inspect # "Math" module, member did not override global
puts x.w.inspect # ["method_func", "local_var"], local variables are always considered before anything else

1 个答案:

答案 0 :(得分:4)

Ruby的方法查找算法实际上真的简单:

  • 检索接收器的class指针
  • 如果方法在那里,请调用它
  • 否则检索superclass指针,然后重复

那就是它。

如果算法到了没有更多超类的地步,但它仍然没有找到该方法,它将再次重新启动整个过程,并以method_missing作为消息和名称原始消息的前缀是参数。但那就是它。这就是整个算法。它非常小而且非常简单, 非常小而且非常简单,因为方法查找是面向对象语言中最常执行的操作。

注意:我完全忽略了Module#prepend / Module#prepend_features,因为我对它的运作方式还不太了解。我只知道它做了什么,这对我来说已经足够好了。

另请注意:我忽略了性能优化,例如在多态内联缓存中缓存方法查找的结果。

好的,但这就是诀窍:那些classsuperclass指针到底指向哪里?好吧,他们指向Object#classClass#superclass方法返回的内容。所以,让我们退一步。

每个对象都有一个class指针,指向对象的类。每个类都有一个指向其超类的superclass指针。

让我们开始一个运行的例子:

class Foo; end

现在,我们有一个班级Foo,其superclass指针指向Object

foo = Foo.new

我们的对象foo&#39; class指针指向Foo

def foo.bar; end

现在事情开始变得有趣了。我们创建了一个单例方法。嗯,实际上,没有单例方法,它实际上只是单例类中的常规方法。那么,这是如何工作的?好吧,现在class指针指向foo的单例类,而foo的单例类superclass指针指向{ {1}}!换句话说,单例类被插入Foo和它的&#34;真实&#34;之间。班foo

但是,当我们询问Foo关于其课程时,它仍会回复foo

Foo

foo.class #=> Foo 方法知道单例类,只需在Object#class指针后跳过它们,直到它找到&#34; normal&#34; class,并返回。

下一个并发症:

superclass

这里发生了什么? Ruby创建了一个 new 类(让我们称之为module Bar; end class Foo include Bar end ),称为 include class 。该类的方法表指针,类变量表指针和常量表指针指向Barʹ的方法表,类变量表和常量表。然后,Ruby使Bar的{​​{1}}指针指向Barʹ的当前超类,然后生成superclass的超类指针指向Foo。换句话说,包含一个模块会创建一个新类,该类作为包含模块的类的超类插入。

这里有一个小问题:你也可以Foo模块进入模块。这是如何运作的?好吧,Ruby只是跟踪模块中包含的模块。然后,当模块被包含在一个类中时,它将递归地为每个包含的模块重复上述步骤。

您需要了解Ruby方法查找的所有内容:

  • 找到班级
  • 关注超类
  • 单例类插入对象
  • 包括在类
  • 上面插入的类

现在让我们来看看你的一些问题:

  

在类上显式调用方法时,会在顺序上有很多插图,其中包含它们所包含的类和模块(因此在每种情况下都是Barʹ调用的内容)。但是当没有明确地调用方法时,例如一个简单的include而不是super什么是搜索顺序?

同样的。 func args是隐式接收器,如果您没有指定接收器,则接收器为self.func args。括号是可选的。换句话说:

self

完全相同
self
  

为什么在我的示例中,调用func args 的成员方法在全局之前找到成员方法,但是self.func(args) 找到没有调用func的全局?

没有&#34;全球方法&#34;在Ruby中。也没有&#34;成员方法&#34;。每个方法都是一个实例方法。期。没有全局,静态,类,单例,成员方法,过程,函数或子例程。

在顶级定义的方法成为类func2的{​​{1}}实例方法。 method_missing继承自private。运行我上面概述的步骤,您将确切地知道发生了什么:

  • 检索Object的类指针:Test
  • Object是否有一个名为x的方法:是的,请调用它。

现在再次:

  • 检索Test的类指针:Test
  • func是否有一个名为x的方法:否!
  • 检索Test的超类指针:Test
  • func2是否有一个名为Test的方法:是的,请调用它。
  

当全局变为模块/类/类型时,为什么根本找不到具有相同名称的成员?

同样,这里没有全球,这里没有成员。这也与模块或类没有任何关系。而且Ruby没有(静态)类型。

Object

是对常量的引用。如果要调用具有相同名称的方法,则必须确保Ruby可以告诉它它是一种方法。 方法可以有两件事:接收器和参数。所以,你可以添加一个接收器:

Object

或参数:

func2

现在Ruby知道你的意思是方法Math 而不是常数self.Math

顺便说一句,这同样适用于局部变量。而且要制定者。如果你想调用一个setter而不是分配一个局部变量,你需要说

Math()