Including/Extending the Kernel doesn't add those methods on main:Object

后端 未结 3 1001
臣服心动
臣服心动 2020-12-31 11:01

I\'m trying to add a method into the Kernel module, but instead of reopening the Kernel and directly defining an instance method, I\'m writing a mo

相关标签:
3条回答
  • 2020-12-31 11:19

    This is more a workaround than a solution, suitable if you don't want to define the function on main:

    module Talk
    
      def self.extended(mod)
        mod.module_eval do
          def hello
            puts "hello there"
          end
        end
      end
    
    end
    
    module Kernel
      extend Talk
    end
    

    Incidentally, I wonder why the behaviour is different in this case. Shouldn't module_eval have the same effect as module Talk; end?

    0 讨论(0)
  • 2020-12-31 11:35

    I will try to explain it a little bit deeper:

    when you include module into some class, Ruby creates special internal include class and adds it to the hierarchy (please note that basically you are not allowed to see include class from Ruby programm, it is hidden class):

    Given A inherits B
    And we have a module C
    When A includes C 
    Then A inherits includeC inherits B 
    

    If included module has other included Modules, then includeModules will be created for these modules as well:

    Given A inherits B
    And we have a module C
    And C includes module D
    When A includes C
    Then A inherits includeC inherits includeD inherits B
    

    Include class C method table is a link to method table of original class C.

    When you extend some object with a module, then this module is included into singleton class of this object, thus:

    class << self; include C; end
    # is the same as
    extend C
    

    Going to your example:

    module Kernel
      extend Talk 
    end
    

    Here you include Talk module into singleton class of Kernel (Kernel is an object of class Module). This is why you can call hello method only on Kernel object: Kernel.hello.

    If we write this:

    module Kernel
      include Talk 
    end
    

    Then Kernel will internally inherit include class includeTalk (class with link to Talk methods).

    But Kernel module is already included into Object - Object inherits its own includeKernel class and includeKernel class has link to method table of Kernel and does not see methods of new include classes of Kernel.

    But now if you will re-include Kernel into Object, all Objects will see methods of Talk:

    > module Talk
    >   def hi
    >     puts 'hi'
    >   end
    > end
     => nil 
    > module Kernel
    >   include Talk
    > end
     => Kernel 
    > hi
    NameError: undefined local variable or method `hi` for main:Object
            from (irb):9
            from /usr/share/ruby-rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>`
    > class Object
    >   include Kernel
    > end
     => Object 
    > hi
    hi
     => nil  
    

    The solution for you probjem might be to extend main object with your new module:

    extend Talk
    

    Hope this is clarified a bit behaviour you observe :)

    UPDATE

    Will try to clarify your questions:

    I'm still a bit confused why I have to re-include Kernel in Object. In cases that don't involve the main Object, I can instantiate an object based on a class and then later reopen that class and include a module, and that object will see methods in my module. Is there something different about how main Object includes the Kernel? I'm also not sure what you mean by "Object inherits its own includeKernel class and includeKernel class..." Why doesn't it see the new included module in Kernel?

    You tell about the case with direct inclusion of module into class of an object:

    module M
      def hi
        puts 'hi'
      end
    end
    
    class C
    end
    
    c = C.new
    c.hi # => UndefinedMethod
    
    class C
      include M
    end
    
    c.hi # => hi
    

    in this case you will have object c of class C. Class C inherits Object (because it is an instance of Class class. c looks for its instance methods in his singleton class -> then in his class C -> then in parents of class C (in this case Object instance methods). When we include module M into class C, then includeM will be a superclass of C and if c will not find his instance method in his singleton class and C class, it will search for instance methods in includeM. includeM has a link to method table of M class (instance of Module class). Thus when c search for instance method hi it finds it in the M module.

    But this is different from case when you include module M into Kernel module. At the program start Object class includes module Kernel: class Object; include Kernel; end. This is why I say that Object inherits from includeKernel. includeKernel has link to method table of Kernel and when you change method table of Kernel, includeKernel will also see these changes:

    module Kernel
      def hi # add hi method to method table of Kernel
        puts 'hi'
      end
    end
    
    hi # => hi # any Object now see method hi
    

    But when you include module M into Kernel, then method table of Kernel is not changed. Instead Kernel will now inherit includeM include class. includeKernel don't see methods of includeM because it does not know about inheritance chain of Kernel and includeM, it only knows the method table of Kernel.

    But when you re-include Kernel into Object, inclusion mechanism will see that Kernel includes M and will create includeM for Object as well. Now Object will inherit includeKernel will inherit includeM will inherit BasicObject.

    0 讨论(0)
  • 2020-12-31 11:40

    You want include, not extend.

    include adds the contents of the module as instance methods (like when you open a class or module normally); extend adds them as class methods (so in your example, you could call Kernel.hello, though this isn't what you want).

    You might now be inclined to try this:

    module Talk
      def hello
        puts "hello there"
      end
    end
    
    module Kernel
      include Talk
    end
    

    But this won't work either, because main has already been instantiated, and include simply alters the ancestry of Kernel.

    You could force main to include your module at runtime, like this:

    # (in global scope)
    class << self
      include Talk
    end
    

    This way, you're altering the metaclass of main, not Kernel (which would imply including it on a ton of other object you may not want).

    If you do, you can also just do an include Talk in global scope, but beware that this will pollute integers and other things with your methods.

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