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

时间:2012-02-11 00:50:12

标签: ruby

我正在尝试在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

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

3 个答案:

答案 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课程中包含模块Kernelclass Object; include Kernel; end。这就是为什么我说Object继承自includeKernelincludeKernel已链接到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的方法,因为它不知道KernelincludeM的继承链,它只知道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具有相同的效果吗?