包含/扩展内核不会在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
类的Kernel
( Kernel
是类Module
一个对象)。 这就是为什么你只能在Kernel对象上调用hello
方法: Kernel.hello
。
如果我们这样写:
module Kernel include Talk end
然后Kernel
将在内部inheritanceinclude class includeTalk(带有Talk
方法链接的类)。
但是内核模块已经包含在Object
- Object
inheritance了自己的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
类的对象c
。 C
类inheritanceObject
(因为它是Class
类的一个实例 .c在他的单例类中查找它的实例方法 - >然后在他的类C - >然后在C类的父类中(在这种情况下是Object
实例方法)。当我们将模块M
包含到C
类中,然后includeM
将是C
的超类,如果c
在他的单例类和C
类中找不到他的实例方法,它将在includeM
搜索实例方法includeM
有一个指向M
方法表的链接class( Module
类的实例)。因此当c
搜索实例方法时,它会在M
模块中找到它。
但这与将模块M
包含在Kernel
模块中的情况不同。 在程序启动时, Object
类包含模块Kernel
: class Object; include Kernel; end
class Object; include Kernel; end
class Object; include Kernel; end
。 这就是为什么我说Object
inheritance自includeKernel
。 includeKernel
有链接到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
方法,因为它不知道Kernel
和includeM
inheritance链,它只知道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
?