2012-02-11 4 views
12

Я пытаюсь добавить метод в модуль 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 `<main> 

Если я проверить instance_methods на Kernel, я могу видеть #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 

Включая Talk модуль в Object тоже работает:

class Object 
    include Talk 
end 

Возможно, я делаю это неправильно, или я что-то просто не хватает, но это поведение сбивает с толку меня.

+0

Джек, @AlexKliuchnikau прибил его; Примите его ответ! И проверьте «Рубиновый хакерский гид», на котором он разместил ссылку! –

ответ

13

Я попытаюсь объяснить это немного глубже:

когда вы include модуля в какой-то класс, 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 какие-то объект с модулем, то этот модуль входят в одноплодном класс этого объекта, таким образом:

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 будет внутренне наследовать включают класс 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

Постараюсь уточнить ваши вопросы:

Я еще немного запутался, почему я должен повторно включать ядра в Object. В случае случаев, которые не связаны с основным объектом, я могу создать объект на основе класса, а затем повторно открыть этот класс и включить модуль , и этот объект увидит методы в моем модуле. Есть ли что-то другое о том, как основной объект включает в себя ядро? Я также не уверен, что вы подразумеваете под «Object наследует свой собственный 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). Когда мы включаем модуль M в класс C, то includeM будет суперклассом C и если c не найдет свой метод экземпляра в его одноплодном классе и C класса, он будет искать методы, например, в includeM. includeM имеет ссылку на метод таблицу из M класс (пример Module класс). Таким образом, когда c поиск метода экземпляра hi он находит его в th e 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 

Но когда вы включаете модуль М в ядро, то метод таблица ядра не изменилась , Вместо этого ядро ​​теперь наследует includeMвключает класс.includeKernel не видят методы includeM, потому что он не знает о цепочке наследования Kernel и includeM, он знает только таблицу методов Kernel.

Но когда вы повторно включить Kernel в Object, механизм включения будет видеть, что Kernel включает M и создаст includeM для Object, а также. Теперь Object наследует includeKernel наследует includeM наследует BasicObject.

+1

Алекс, спасибо за подробное объяснение. Теперь у меня есть лучшее понимание, но я все еще немного смущен, почему мне нужно повторно включить «Ядро» в «Объект». В случаях, которые не связаны с основным «объектом», я могу создать экземпляр объекта на основе класса, а затем снова открыть этот класс и включить модуль, и этот объект увидит методы в моем модуле. Есть ли что-то другое в том, как главный «объект» включает в себя «ядро»? Я также не уверен, что вы подразумеваете под «Object наследует свой собственный класс includeKernel и включает класс ядра ...» Почему он не видит новый включенный модуль в «Kernel»? –

+0

@JackChu, я обновил свой ответ с более подробным объяснением –

+0

@AlexKliuchnikau, спасибо за это объяснение. Это отличается от того, что написано в * «Язык программирования Ruby» * - это изменилось? Но ваш ответ работает. @JackChu, если вы попробуете добавить метод экземпляра 'hello' в' Kernel', а затем попробуйте 'Object.new.hello', он не работает. Но если вы повторно включите «Ядро» в «Объект», оно будет работать. Таким образом, это не ограничивается главным объектом. Это все очень сбивает меня с толку, потому что в прошлом я наблюдал за другим поведением. –

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, поэтому я использовал расширение. –

+1

Помогает ли трюк с помощью 'class << self' решить вашу проблему? Если вы хотите атаковать только объект 'main', это скорее всего то, что вы хотите. Одиночные методы - это методы, определенные на метаклассе ядра (которые можно использовать для работы с самим модулем ядра), тогда как методы экземпляра доступны для объектов, чьи классы наследуют (или включают) ядро. 'main' находится в этой категории. Извините, если я не объясню это хорошо. –

+0

Вы правы, я хочу 'include', а не' extend'. Таким образом, 'include' добавляет методы анонимному прокси-классу в объект класса Kernel. 'extend' добавляет эти методы в одноярусный класс ядра. Это правильно? Трюк действительно работает, но я пытаюсь добавить это в камень в файле core_ext/kernel, а основной объект не является областью в этом случае. –

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

Он имеет тот же эффект. То, что вы делаете, - это определение методов самого модуля «Ядро», а «Talk» не содержит методов. Принимая во внимание, что 'include' и' extend' изменяют цепочку наследования, чтобы сказать «также смотрите здесь методы», вы добавляете метод непосредственно в «Ядро», так же, как если бы вы помещали объявления в 'Kernel'. –

Смежные вопросы