问题
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
回答1:
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'
来源:https://stackoverflow.com/questions/39408538/ruby-method-lookup-in-regards-to-globals-scopes