关于全局/范围的 Ruby 方法查找 [英] Ruby method lookup in regards to globals/scopes

查看:48
本文介绍了关于全局/范围的 Ruby 方法查找的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试全面了解 Ruby 如何定位方法/符号,但在涉及多个级别(尤其是全局/文件范围)时遇到困难.

当在一个类上显式调用方法时,有很多关于搜索类和它们包含的模块的顺序的说明(因此在每种情况下都准确地调用了super).但是当没有显式调用方法时,例如一个普通的 func args 而不是 self.func args 搜索顺序是什么?

为什么在我下面的例子中,调用func的成员方法在全局之前找到了成员方法,而func2却在没有method_missing 被叫?而当全局是模块/类/类型时,为什么根本找不到同名的成员?

是否有任何官方文档说明语言在遇到funcfunc()func arg"等时会做什么?. 在方法中?有很多第三方博客,但他们只真正讨论了include Moduleclass Type 的单个实例.基本类型.

def func;全局函数"结束def func2;全局函数2"结束课堂测试定义 x;功能结束定义 y;func2 结束定义 z;数学结束定义func = "local_var"[函数(),函数]结尾def func(arg=nil);方法函数"结束def func=(x);把分配功能="结束定义数学;方法数学"结束def method_missing(sym, *args, &block)puts "method_missing #{sym}"super(sym, *args, &block)结尾结尾x = 测试.newputs x.x.inspect # "method func", 成员覆盖全局puts x.y.inspect # "global func 2", "method_missing" 没有被调用puts x.z.inspect # "Math" 模块,成员没有覆盖全局puts x.w.inspect # ["method_func", "local_var"], 局部变量总是在其他任何事情之前考虑

解决方案

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

  • 获取接收者的class指针
  • 如果方法存在,则调用它
  • 否则检索superclass指针,并重复

就是这样.

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

注意:我完全忽略了Module#prepend/Module#prepend_features 因为我只是不太了解它是如何工作的.我只知道它的作用,这对我来说已经足够了.

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

好的,但这里有一个技巧:那些 classsuperclass 指针到底指向哪里?好吧,他们没有指向Object#classClass#superclass 方法返回.所以,让我们退后一步.

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

让我们开始一个运行示例:

class Foo;结尾

现在,我们有了 Foo 类,它的 superclass 指针指向 Object.

foo = Foo.new

我们的对象fooclass指针指向Foo.

def foo.bar;结尾

现在事情开始变得有趣了.我们创建了一个单例方法.嗯,实际上,没有单例方法这样的东西,它实际上只是单例类中的一个普通方法.那么,这是如何工作的呢?那么,现在 class 指针指向 foo 的单例类和 foo 的单例类的 superclass 指针指向Foo!换句话说,单例类被插入在 foo 和它的真实"类 Foo 之间.

然而,当我们询问foo关于它的类时,它仍然回答Foo:

foo.class #=>富

Object#class 方法知道单例类,并简单地跳过它们,跟随 superclass 指针,直到它找到一个普通"类,然后返回.

下一个并发症:

模块栏;结尾Foo类包括酒吧结尾

这里发生了什么?Ruby 创建了一个 类(我们称之为 Barʹ),称为 include 类.该类的方法表指针、类变量表指针和常量表指针指向Bar的方法表、类变量表和常量表.然后,Ruby 使 Barʹsuperclass 指针指向 Foo 的当前超类,然后使 Foo's 超类指针指向 Barʹ.换句话说,包含一个模块会创建一个新类,该类被插入为包含该模块的类的超类.

这里有点复杂:您还可以include 模块到模块中.这是如何运作的?好吧,Ruby 只是跟踪包含在模块中的模块.然后,当模块被包含到一个类中时,它会递归地对每个包含的模块重复上述步骤.

这就是您需要了解的有关 Ruby 方法查找的全部内容:

  • 找到班级
  • 跟随超类
  • 单个类插入对象上方
  • 在类上方插入包含类

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

<块引用>

当在一个类上显式调用方法时,有很多关于搜索类和它们包含的模块的顺序的说明(因此在每种情况下都准确地调用了super).但是当没有显式调用方法时,例如一个普通的 func args 而不是 self.func args 搜索顺序是什么?

一样.self 是隐式接收者,如果不指定接收者,接收者为self.括号是可选的.换句话说:

func 参数

完全一样

self.func(args)

<块引用>

为什么在我下面的例子中,调用func的成员方法在全局之前找到了成员方法,而func2却在没有method_missing 被叫?

Ruby 中没有全局方法"这样的东西.也没有成员方法"这样的东西.每个方法都是一个实例方法.时期.没有全局、静态、类、单例、成员方法、过程、函数或子例程.

在顶层定义的方法成为类Objectprivate实例方法.Test 继承自 Object.运行我上面概述的步骤,你会发现到底发生了什么:

  • 获取x的类指针:Test
  • Test 是否有一个名为 func 的方法:是的,所以调用它.

再说一遍:

  • 获取x的类指针:Test
  • Test 是否有一个名为 func2 的方法:没有!
  • 获取Test的超类指针:Object
  • Object 是否有一个名为 func2 的方法:是的,所以调用它.
<块引用>

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

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

数学

是对常量的引用.如果你想调用一个同名的方法,你必须确保 Ruby 可以判断它是一个方法.only 方法可以拥有两件东西:接收器和参数.因此,您可以添加接收器:

self.Math

或参数:

数学()

现在 Ruby 知道您指的是方法 Math 而不是常量 Math.

顺便说一下,这同样适用于局部变量.和二传手.如果要调用 setter 而不是分配局部变量,则需要说

self.func = 'setter 方法'

I am trying to get a full understanding of how Ruby locates methods/symbols but am struggling when it involves multiple levels, especially the global/file scope.

When calling methods explicitly on a class, there are lots of illustrations on the order in which the classes, and modules included by them are searched (and thus exactly what super calls in each case). But when not explicitly calling a method, e.g. a plain func args rather than self.func args what is the search order?

Why does in my example below, the member method calling func find the member method before the global, but func2 finds the global without method_missing being called? And when the global is instead an module/class/type, why is the member with the same name not found at all?

Is there any official documentation as to exactly what the language does when it encounters "func, func(), func arg" etc. in a method? There is a lot of third-party blogs, but they only really talked about single instances with include Module and class 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

解决方案

Ruby's method lookup algorithm is actually really simple:

  • retrieve the class pointer of the receiver
  • if the method is there, invoke it
  • otherwise retrieve the superclass pointer, and repeat

That's it.

If the algorithm comes to a point where there is no more superclass, but it still hasn't found the method yet, it will restart the whole process again, with method_missing as the message and the name of the original message prepended to the arguments. But that's it. That's the whole algorithm. It is very small and very simple, and it has to be very small and very simple because method lookup is the single most often executed operation in an object-oriented language.

Note: I am completely ignoring Module#prepend / Module#prepend_features since I just don't know enough about how it works. I only know what it does, and that's good enough for me.

Also note: I am ignoring performance optimizations such as caching the result of a method lookup in something like a Polymorphic Inline Cache.

Okay, but here's the trick: where exactly do those class and superclass pointers point to? Well, they do not point to what the Object#class and Class#superclass methods return. So, let's step back a little.

Every object has a class pointer that points to the class of the object. And every class has a superclass pointer that points to its superclass.

Let's start a running example:

class Foo; end

Now, we have class Foo, and its superclass pointer points to Object.

foo = Foo.new

And our object foo's class pointer points to Foo.

def foo.bar; end

Now things start to get interesting. We have created a singleton method. Well, actually, there is no such thing as a singleton method, it's really just a normal method in the singleton class. So, how does this work? Well, now the class pointer points to foo's singleton class and foo's singleton class's superclass pointer points to Foo! In other words, the singleton class was inserted in between foo and its "real" class Foo.

However, when we ask foo about its class, it still responds Foo:

foo.class #=> Foo

The Object#class method knows about singleton classes, and simply skips over them, following the superclass pointer until it finds a "normal" class, and returns that.

Next complication:

module Bar; end

class Foo
  include Bar
end

What happens here? Ruby creates a new class (let's call it Barʹ), called an include class. This class's method table pointer, class variable table pointer, and constant table pointer point to Bar's method table, class variable table, and constant table. Then, Ruby makes Barʹ's superclass pointer point to Foo's current superclass, and then makes Foo's superclass pointer point to Barʹ. In other words, including a module creates a new class that gets inserted as the superclass of the class the module is included into.

There's a slight complication here: you can also include modules into modules. How does that work? Well, Ruby simply keeps track of the modules that were included into a module. And then, when the module is included into a class, it will recursively repeat the steps above for every included module.

And that's all you need to know about the Ruby method lookup:

  • find the class
  • follow the superclass
  • singleton classes insert above objects
  • include classes insert above classes

Now let's look at some of your questions:

When calling methods explicitly on a class, there are lots of illustrations on the order in which the classes, and modules included by them are searched (and thus exactly what super calls in each case). But when not explicitly calling a method, e.g. a plain func args rather than self.func args what is the search order?

The same. self is the implicit receiver, if you don't specify a receiver, the receiver is self. And parentheses are optional. In other words:

func args

is exactly the same as

self.func(args)

Why does in my example below, the member method calling func find the member method before the global, but func2 finds the global without method_missing being called?

There is no such thing as a "global method" in Ruby. There is also no such thing as a "member method". Every method is an instance method. Period. There are no global, static, class, singleton, member methods, procedures, functions, or subroutines.

A method defined at the top-level becomes a private instance method of class Object. Test inherits from Object. Run the steps I outlined above, and you will find exactly what is going on:

  • Retrieve x's class pointer: Test
  • Does Test have a method called func: Yes, so invoke it.

Now again:

  • Retrieve x's class pointer: Test
  • Does Test have a method called func2: No!
  • Retrieve Test's superclass pointer: Object
  • Does Object have a method called func2: Yes, so invoke it.

And when the global is instead an module/class/type, why is the member with the same name not found at all?

Again, there is no global here, there are no members here. This also doesn't have anything to do with modules or classes. And Ruby doesn't have (static) types.

Math

is a reference to a constant. If you want to call a method with the same name, you have to ensure that Ruby can tell that it's a method. There are two things that only methods can have: a receiver and arguments. So, you can either add a receiver:

self.Math

or arguments:

Math()

and now Ruby knows that you mean the method Math and not the constant Math.

The same applies to local variables, by the way. And to setters. If you want to call a setter instead of assigning a local variable, you need to say

self.func = 'setter method'

这篇关于关于全局/范围的 Ruby 方法查找的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆