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

Jac*_*Chu 12 ruby

我想添加一个方法到Kernel模块,但不是重新打开Kernel,直接定义一个实例方法,我正在写一个模块,我想Kernelextend/include该模块.

module Talk
  def hello
    puts "hello there"
  end
end

module Kernel
  extend Talk
end
Run Code Online (Sandbox Code Playgroud)

当我在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 `<main>
Run Code Online (Sandbox Code Playgroud)

如果我检查instance_methodsKernel,我可以看到#hello加入了Kernel,但不是在main Object.

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

module Kernel
  include Talk
end
Run Code Online (Sandbox Code Playgroud)

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

module Kernel
  def hello
    puts "hello there"
  end
end
Run Code Online (Sandbox Code Playgroud)

然后它确实包含在main Object.

$ hello
hello there
 => nil 
Run Code Online (Sandbox Code Playgroud)

包括Talk模块也在Object工作中:

class Object
  include Talk
end
Run Code Online (Sandbox Code Playgroud)

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

Ali*_*kau 13

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

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

Given A inherits B
And we have a module C
When A includes C 
Then A inherits includeC inherits B 
Run Code Online (Sandbox Code Playgroud)

如果包含的模块包含其他模块,那么也将为这些模块创建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
Run Code Online (Sandbox Code Playgroud)

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

当你extend有一个模块的对象,那么这个模块被包含在这个对象的单例类中,因此:

class << self; include C; end
# is the same as
extend C
Run Code Online (Sandbox Code Playgroud)

举个例子:

module Kernel
  extend Talk 
end
Run Code Online (Sandbox Code Playgroud)

在这里,您将Talk模块包含到singleton类中Kernel(Kernel是类的对象Module).这就是为什么你hello只能在Kernel对象上调用方法:Kernel.hello.

如果我们这样写:

module Kernel
  include Talk 
end
Run Code Online (Sandbox Code Playgroud)

然后Kernel将内部继承包括类 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  
Run Code Online (Sandbox Code Playgroud)

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

extend Talk
Run Code Online (Sandbox Code Playgroud)

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

UPDATE

将尝试澄清您的问题:

我仍然有点困惑为什么我必须在对象中重新包含内核.在不涉及主Object的情况下,我可以基于类实例化一个对象,然后重新打开该类并包含一个模块,该对象将在我的模块中看到方法.关于主要对象如何包含内核有什么不同吗?我也不确定你的意思是"Object继承了自己的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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,你将拥有c类的对象C.类C继承Object(因为它是一个实例Class类C相对于它的实例方法在他的单例类- >然后在他的C类- >然后在C级的父母(在这种情况下Object实例方法)当我们包括模块.M成class C,然后includeM将是一个超类,C如果c在他的单例类和C类中找不到他的实例方法,它将搜索实例方法includeM.includeM有一个M类的方法表的链接(类的实例Module).因此当c搜索实例方法时hi它在M模块中找到它.

但这与将模块包含MKernel模块中的情况不同.在程序启动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
Run Code Online (Sandbox Code Playgroud)

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

但是当你重新Kernel加入时Object,包含机制将会看到Kernel包含M并将创建includeM Object.现在Object将继承includeKernel将继承includeM将继承BasicObject.