2012-02-11 20 views
12

我試圖添加一個方法到Kernel模塊,而不是重新打開Kernel並直接定義一個實例方法,我正在編寫一個模塊我想要Kernelextend/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 `<main> 

如果我檢查Kernelinstance_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 

包括ObjectTalk模塊工作過:

class Object 
    include Talk 
end 

也許我這樣做不對,還是我失去了一些東西簡單,但這種行爲是混淆了我。

+0

傑克@AlexKliuchnikau釘了它;請接受他的回答!並查看他發佈了鏈接的「Ruby黑客指南」! – 2012-02-14 09:05:13

回答

13

我會盡量解釋它一點點更深:

當你include模塊插入一些類,紅寶石,創建特殊的內部包括類,並將其添加到層次結構(請注意,基本上你是不允許看包括類從紅寶石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一些對象,具有一個模塊,則該模塊被納入單例類這個對象,因而:

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

去你的例子:

module Kernel 
    extend Talk 
end 

在這裏包括Talk模塊插入的單個類的KernelKernelModule類的一個對象)。這就是爲什麼您只能在內核對象Kernel.hello上調用hello方法的原因。

如果我們寫:

module Kernel 
    include Talk 
end 

然後Kernel將在內部繼承包括類 includeTalk(類鏈接Talk方法)。

但已經包含內核模塊爲Object - Object繼承自己的includeKernel類includeKernel類有鏈接的Kernel方法表,並沒有看到法新包括Kernel類。

但如果你現在將重新加入內核爲對象,所有對象都會看到方法講座的:

> 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 

probjem可能是你的解決方案來擴展主要對象與您的新模塊:

extend Talk 

希望這澄清了一點問題,您觀察:)

UPDATE

將試圖澄清你的問題:

我還是有點困惑,爲什麼我要重新包括內核的對象。在 不涉及主要對象的情況下,我可以基於類實例化對象 ,然後重新打開該類幷包含 模塊,該對象將在我的模塊中看到方法。有沒有 關於主對象如何包含內核的東西有所不同?我是 也不知道你的意思是「對象繼承自己的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繼承Object(因爲它是一個實例Class類。c在他的單例類 - >然後在他的類C - >然後在類C的父類(在這種情況下爲Object實例方法)中查找其實例方法。當我們包括模塊MC類,那麼includeM將是C超類,如果c在他的單例類和C類不會找到他的實例方法,它會在includeM實例方法搜索。includeM有一個鏈接方法表M類(Module類的實例)。因此,當c搜索實例方法hi它發現它在第e 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進入內核,那麼方法內核的表沒有改變。相反,Kernel現在將繼承includeM,包括類includeKernel由於不知道KernelincludeM的繼承鏈,因此只知道Kernel的方法表。

但是,當你重新加入到KernelObject,包含機制將看到Kernel包括M併爲Object創建includeM爲好。現在Object將繼承includeKernel將繼承includeM將繼承BasicObject

+1

Alex,謝謝你的詳細解釋。我現在有了一個更好的理解,但是我仍然有點困惑,爲什麼我必須在'Object'中重新包含'Kernel'。在不涉及主對象的情況下,我可以基於類實例化一個對象,然後重新打開該類幷包含一個模塊,該對象將在我的模塊中看到方法。主'對象'包含'內核'有什麼不同嗎? 我也不確定你的意思是「對象繼承它自己的includeKernel類和includeKernel類...」爲什麼它看不到'Kernel'中包含的新模塊? – 2012-02-13 21:49:49

+0

@JackChu,我更新了我的答案,更詳細的解釋 – 2012-02-14 08:02:46

+0

@AlexKliuchnikau,謝謝你的解釋。它與*「The Ruby Programming Language」中寫的內容不同 - 是否已經改變了?但你的答案是有效的。 @JackChu,如果你嘗試在'Kernel'中添加一個實例方法'hello',然後嘗試'Object.new.hello',則它不起作用。但是如果你在'Object'中重新包含'Kernel',它就可以工作。所以這不僅限於主要的對象。這對我來說都很混亂,因爲我在過去觀察過不同的行爲。 – 2012-02-14 08:15:10

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,但要注意這會污染你的方法的整數和其他東西。

+0

我也試過包括,但我得到了同樣的錯誤。這有點令人困惑,因爲如果你看看Kernel.singleton_methods,你會看到像lambda,proc和sleep這樣的方法。當你看看Kernel.instance_methods,你會得到哈希,克隆,dup,is_a?和kind_of?實例方法看起來像我會調用對象的方法。單身方法看起來像沒有接收者的情況下我會打電話的方法,或者自己是接收者。 它看起來像我想將我的方法添加到singleton_methods,所以我使用擴展。 – 2012-02-11 01:04:39

+1

使用'class << self'解決你的問題嗎?如果你只想攻擊它的'main'對象,這可能是你想要的。 單例方法是在內核元類上定義的方法(您可以使用它來對內核模塊本身進行操作),而實例方法可用於其類繼承自(或包含)內核的對象。 'main'屬於這個類別。如果我沒有解釋得很好,我很抱歉。 – 2012-02-11 01:28:10

+0

你說得對,我確實想要「包括」而不是「延伸」。因此,'include'將方法添加到Kernel類對象上的匿名代理類。 'extend'將這些方法添加到內核單例類中。是對的嗎?這個技巧確實有效,但我試圖將它添加到core_ext/kernel文件中的gem中,並且主對象不是這種情況下的作用域。 – 2012-02-13 21:22:21

3

這更是一個解決辦法比的解決方案,適合的,如果你不希望定義上主要的功能:

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具有相同的效果?

+0

它的確有同樣的效果。你正在做的是在'Kernel'模塊本身上定義方法,並且'Talk'不包含任何方法。 雖然'include'和'extend'修飾繼承鏈以表示「也可以在這裏尋找方法」,你直接向'Kernel'添加一個方法,就像你把聲明放在'Kernel'本身一樣。 – 2012-02-11 01:16:39