包含/扩展内核不会在main:Object上添加这些方法

我正在尝试将方法添加到Kernel模块中,但是我没有重新打开Kernel并直接定义实例方法,而是编写了一个模块,我希望Kernel extend/include该模块。

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

当我在IRB中运行时:

 $ hello NameError: undefined local variable or method `hello' for main:Object from (irb):12 from /Users/JackC/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in ` 

如果我检查Kernel上的instance_methods ,我可以看到#hello已添加到Kernel ,但不会添加到main Object

我也试过使用include ,但同样的事情发生了:

 module Kernel include Talk end 

但是,如果我直接定义它:

 module Kernel def hello puts "hello there" end end 

然后它确实包含在main Object

 $ hello hello there => nil 

Object包含Talk模块也适用:

 class Object include Talk end 

也许我做错了,或者我错过了一些简单的事情,但这种行为让我感到困惑。

我会尝试更深入地解释一下:

当你include模块include到某个类中时,Ruby会创建特殊的内部包含类并将其添加到层次结构中(请注意,基本上你不允许从Ruby程序中看到包含类 ,它是隐藏类):

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

如果包含的模块包含其他模块,那么也将为这些模块创建includeModules:

 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 

包含类 C方法表是原始类C的方法表的链接

当您使用模块extend某个对象时,此模块将包含在此对象的singleton类中,因此:

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

举个例子:

 module Kernel extend Talk end 

这里你将Talk模块包含在单Kernel类的KernelKernel是类Module一个对象)。 这就是为什么你只能在Kernel对象上调用hello方法: Kernel.hello

如果我们这样写:

 module Kernel include Talk end 

然后Kernel将在内部inheritanceinclude class includeTalk(带有Talk方法链接的类)。

但是内核模块已经包含在Object - Objectinheritance了自己的includeKernel类,includeKernel类有链接到Kernel方法表,并没有看到新的包含Kernel类的方法。

但是现在如果你将内核重新包含到Object中,所有对象都会看到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 `
` > class Object > include Kernel > end => Object > hi hi => nil

您的问题的解决方案可能是使用新模块扩展主对象

 extend Talk 

希望这可以澄清你观察到的一点行为:)

UPDATE

将尝试澄清您的问题:

我仍然有点困惑为什么我必须在对象中重新包含内核。 在不涉及主Object的情况下,我可以基于类实例化一个对象,然后重新打开该类并包含一个模块,该对象将在我的模块中看到方法。 关于主要对象如何包含内核有什么不同吗? 我也不确定你的意思是“Objectinheritance了自己的includeKernel类和includeKernel类......”为什么它没有在内核中看到新包含的模块?

您可以通过将模块直接包含在对象的类中来说明这种情况:

 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 

在这种情况下,您将拥有c类的对象cC类inheritanceObject (因为它是Class类的一个实例 .c在他的单例类中查找它的实例方法 - >然后在他的类C - >然后在C类的父类中(在这种情况下是Object实例方法)。当我们将模块M包含到C类中,然后includeM将是C的超类,如果c在他的单例类和C类中找不到他的实例方法,它将在includeM搜索实例方法includeM有一个指向M方法表的链接class( Module类的实例)。因此当c搜索实例方法时,它会在M模块中找到它。

但这与将模块M包含在Kernel模块中的情况不同。 在程序启动时, Object类包含模块Kernelclass Object; include Kernel; end class Object; include Kernel; end class Object; include Kernel; end 。 这就是为什么我说Objectinheritance自includeKernelincludeKernel有链接到Kernel方法表,当你改变Kernel的方法表时includeKernel也会看到这些变化:

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

但是当你将模块M包含在内核中时,内核的方法表不会改变。 相反,内核现在将inheritanceincludeM include类includeKernel没有看到includeM方法,因为它不知道KernelincludeMinheritance链,它只知道Kernel的方法表。

但是当你将Kernel重新包含到Object ,包含机制将看到Kernel包含M并且还将为Object创建includeM。 现在Object将inheritanceincludeKernel将inheritanceincludeM将inheritanceBasicObject

你想要include ,而不是extend

include将模块的内容添加为实例方法(比如正常打开类或模块时); extend将它们添加为类方法(因此在您的示例中,您可以调用Kernel.hello ,尽管这不是您想要的)。

您现在可能倾向于尝试这样做:

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

但是这也不会起作用,因为main已经被实例化了,并且只是改变了Kernel的祖先。

你可以强制main在运行时包含你的模块,如下所示:

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

这样,你就改变了main的元类,而不是Kernel (这意味着将它包含在你可能不想要的大量其他对象上)。

如果你这样做,你也可以在全局范围内做一个include Talk ,但要注意这会用你的方法污染整数和其他东西。

这是一种解决方法而不是解决方案,如果您不想在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 

顺便说一句,我想知道为什么在这种情况下行为是不同的。 module_eval不应该与module Talk; end具有相同的效果module Talk; end module Talk; end