关于全局/范围的 Ruby 方法查找 [英] Ruby method lookup in regards to globals/scopes
问题描述
我正在尝试全面了解 Ruby 如何定位方法/符号,但在涉及多个级别(尤其是全局/文件范围)时遇到困难.
当在一个类上显式调用方法时,有很多关于搜索类和它们包含的模块的顺序的说明(因此在每种情况下都准确地调用了super
).但是当没有显式调用方法时,例如一个普通的 func args
而不是 self.func args
搜索顺序是什么?
为什么在我下面的例子中,调用func
的成员方法在全局之前找到了成员方法,而func2
却在没有method_missing
被叫?而当全局是模块/类/类型时,为什么根本找不到同名的成员?
是否有任何官方文档说明语言在遇到func
、func()
、func arg
"等时会做什么?. 在方法中?有很多第三方博客,但他们只真正讨论了include Module
和class 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
因为我只是不太了解它是如何工作的.我只知道它的作用,这对我来说已经足够了.
另请注意:我忽略了性能优化,例如在多态内联缓存之类的东西中缓存方法查找的结果.
好的,但这里有一个技巧:那些 class
和 superclass
指针到底指向哪里?好吧,他们没有指向Object#class
和 Class#superclass
方法返回.所以,让我们退后一步.
每个对象都有一个 class
指针,指向对象的类.每个类都有一个superclass
指针指向它的超类.
让我们开始一个运行示例:
class Foo;结尾
现在,我们有了 Foo
类,它的 superclass
指针指向 Object
.
foo = Foo.new
我们的对象foo
的class
指针指向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 中没有全局方法"这样的东西.也没有成员方法"这样的东西.每个方法都是一个实例方法.时期.没有全局、静态、类、单例、成员方法、过程、函数或子例程.
在顶层定义的方法成为类Object
的private
实例方法.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 plainfunc args
rather thanself.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, butfunc2
finds the global withoutmethod_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 calledfunc
: Yes, so invoke it.
Now again:
- Retrieve
x
's class pointer:Test
- Does
Test
have a method calledfunc2
: No! - Retrieve
Test
's superclass pointer:Object
- Does
Object
have a method calledfunc2
: 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屋!