What are the *actual* steps in ruby's method lookup?

前端 未结 1 671
轻奢々
轻奢々 2021-02-04 19:09

I\'ve read stackoverflow posts on this topic as well as several articles which include A Primer on Ruby Method Lookup, What is the method lookup path in Ruby. In addition, I che

1条回答
  •  遇见更好的自我
    2021-02-04 20:06

    There's a ... gem ... in that second ref that I think gets to the core of the answer: ancestors of the singleton class. Applied to your object, it would be:

    fido_instance.singleton_class.ancestors

    This will always give you the order of method lookup that Ruby uses. It's pretty simple when you view it this way, and that's the bottom line answer to your question. Ruby will start at the singleton_class and work its way up the ancestors looking for that method. Using your diagram:

    fido.singleton_class.ancestors
    => [Fetch, WagTail, DogClass, Object, Kernel, BasicObject]
    

    (Note1: Bark is not part of this output because you used extend instead of include. More on this in a second.)

    (Note2: If it doesn't find it all the way up to BasicObject, then it will call method_missing up the same ancestry chain.)

    It's no different when calling a method on a class, because in Ruby a class it just an instance of class Class. So DogClass.method1 will search for method1 on DogClass.singleton_class and then up its ancestry chain, just like before.

    DogClass.singleton_class.ancestors
    => [Bark, Class, Module, Object, Kernel, BasicObject]
    

    Since you used extend for Bark, this is where we find it! So if Bark defined a method bark, then you can call DogClass.bark because that method is defined in DogClass's singleton_class' ancestors.

    To understand what that ancestry tree will be (instead of relying on printing it out every time), you simply need to know how the ancestry is modified by subclassing, extend, include, prepend, etc.

    1. Subclassing gives the child class the entire ancestry chain of its superclass.
    2. includeing a module in a class C adds that module into the ancestry chain after C and before everything else.
    3. prepending a module in a class C adds that module into the ancestry chain before everything, including C and any currently prepended modules.
    4. def x.method1 adds method1 to x.singleton_class. Similarly x.extend(M) will add M to the ancestry of x.singleton_class (but not to x.class). Note that the latter is exactly what happened with Bark and DogClass.singleton_class, but can equally apply to any object.

    Leaving out extend from the above list because it does not modify the object's ancestry chain. It does modify the ancestry of that object's singleton_class -- as we saw, Bark was included in DogClass.singleton_class.ancestors.


    Tangent:

    The bit about class methods above is the key to me for understanding how important singleton classes are to Ruby. You obviously can't define bark on DogClass.class, because DogClass.class == Class and we don't want bark on Class! So how can we allow DogClass to be an instance of Class, allowing it to have a (class) method bark that is defined for DogClass but not unrelated classes? Using the singleton class! In this way, defining a "class method", like by def self.x inside class C, is sort of like C.singleton_class.send(:define_method, :x) {...}.

    0 讨论(0)
提交回复
热议问题