我正在尝试在Kernel
模块中添加一个方法,但是我没有重新打开Kernel
并直接定义实例方法,而是编写了一个模块,我希望{{1} } Kernel
那个模块。
extend/include
当我在IRB中运行时:
module Talk
def hello
puts "hello there"
end
end
module Kernel
extend Talk
end
如果我查看$ 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 `<main>
上的instance_methods
,我可以看到#hello已添加到Kernel
,但未添加到Kernel
。
我也尝试使用main Object
,但同样的事情发生了:
include
但是,如果我直接定义它:
module Kernel
include Talk
end
然后它会包含在module Kernel
def hello
puts "hello there"
end
end
。
main Object
在$ hello
hello there
=> nil
中包含Talk
模块也适用:
Object
也许我做错了,或者我错过了一些简单的事情,但这种行为让我感到困惑。
答案 0 :(得分:12)
我会尝试更深入地解释一下:
当你将include
模块放入某个类时,Ruby会创建特殊的内部 include class 并将其添加到层次结构中(请注意,基本上你不允许看到 include class 来自Ruby programm,它是隐藏的类):
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 中,因此:
class << self; include C; end
# is the same as
extend C
转到你的例子:
module Kernel
extend Talk
end
在这里,您将Talk
模块包含到Kernel
的单一类中(Kernel
是类Module
的对象)。这就是为什么你只能在内核对象上调用hello
方法:Kernel.hello
。
如果我们这样写:
module Kernel
include Talk
end
然后Kernel
将在内部继承 include class includeTalk(包含Talk
方法链接的类。)
但是内核模块已经包含在Object
中 - Object
继承了自己的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 `<main>`
> class Object
> include Kernel
> end
=> Object
> hi
hi
=> nil
您的问题解决方案可能是使用新模块扩展主对象:
extend Talk
希望这可以澄清你观察到的一点行为:)
<强>更新强>
将尝试澄清您的问题:
为什么我必须在对象中重新包含内核,我仍然有点困惑。在 不涉及主Object的情况,我可以实例化一个对象 基于一个类,然后重新打开该类并包含一个 模块,该对象将在我的模块中看到方法。在那儿 关于主要对象如何包含内核的不同之处?我 也不确定你的意思“对象继承了自己的includeKernel class和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
继承Object
(因为它是Class
类的实例 .c在他的单例类中查找其实例方法 - >然后在他的类中C - &gt;然后在C类的父类中(在本例中为Object
实例方法)。当我们将模块M
包含到类C
中时,includeM
将是一个超类C
如果c
在他的单例类和C
类中找不到他的实例方法,它将在includeM
中搜索实例方法。includeM
指向M
类的方法表的链接(Module
类的实例)。因此当c
搜索实例方法hi
时,它会在M
模块中找到它
但这与将M
模块包含到Kernel
模块中的情况不同。
在程序开始Object
课程中包含模块Kernel
:class Object; include Kernel; end
。这就是为什么我说Object
继承自includeKernel
。 includeKernel
已链接到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包含在内核中时,内核的方法表不会改变。相反,内核现在将继承includeM
包含类。 includeKernel
没有看到includeM
的方法,因为它不知道Kernel
和includeM
的继承链,它只知道Kernel
的方法表。
但是,如果您将Kernel
重新加入Object
,则包含机制会看到Kernel
包含M
,并且还会为Object
创建includeM。现在Object
将继承includeKernel
将继承includeM
将继承BasicObject
。
答案 1 :(得分:3)
您需要include
,而不是extend
。
include
将模块的内容添加为实例方法(比如正常打开类或模块时); extend
将它们添加为类方法(因此在您的示例中,您可以调用Kernel.hello
,尽管这不是您想要的)。
您现在可能会尝试这样做:
module Talk
def hello
puts "hello there"
end
end
module Kernel
include Talk
end
但这也不起作用,因为main
已经实例化,include
只是改变了Kernel
的祖先。
您可以强制main
在运行时包含模块,如下所示:
# (in global scope)
class << self
include Talk
end
通过这种方式,您正在改变main
的元类,而不是Kernel
(这意味着将其包含在您可能不需要的大量其他对象上)。
如果你这样做,你也可以在全局范围内做include Talk
,但要注意这会用你的方法污染整数和其他东西。
答案 2 :(得分:3)
这更像是一种解决方法,而不是解决方案,如果您不想在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
具有相同的效果吗?