Включение / расширение ядра не добавляет эти методы в main: Object - PullRequest
12 голосов
/ 11 февраля 2012

Я пытаюсь добавить метод в модуль Kernel, но вместо того, чтобы заново открывать Kernel и напрямую определять метод экземпляра, я пишу модуль и хочу, чтобы Kernel - extend/includemodule.

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

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

Ответы [ 3 ]

12 голосов
/ 11 февраля 2012

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

когда вы include добавляете модуль в некоторый класс, Ruby создает специальный внутренний включающий класс и добавляет его в иерархию (обратите внимание, что в принципе вам не разрешено видеть включающий класс из программы 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: Kernel.hello.

Если мы напишем это:

module Kernel
  include Talk 
end

Тогда Kernel будет внутренне наследоваться включать класс includeTalk (класс со ссылкой на Talk методы).

Но модуль ядра уже включен в Object - Object наследует свой собственный класс includeKernel, а класс includeKernel имеет ссылку на таблицу методов Kernel и не видит методы новых классов include 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

Надеюсь, это прояснится немного поведения, которое вы наблюдаете:)

UPDATE

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

Я все еще немного сбит с толку, почему мне нужно заново включить ядро ​​в Object. В случаи, которые не связаны с основным объектом, я могу создать экземпляр объекта на основе класса, а затем снова открыть этот класс и включить модуль, и этот объект будет видеть методы в моем модуле. Есть что-то другое о том, как основной объект включает в себя ядро? я также не уверен, что вы подразумеваете под "Объект наследует свой собственный includeKernel class и includeKernel class ... "Почему он не видит новое включенное модуль в ядре?

Расскажите о случае с прямым включением модуля в класс объекта:

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 не найдет его метод экземпляра в своем классе. класс singleton и класс 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 include class . includeKernel не видит методы includeM, потому что он не знает о цепочке наследования Kernel и includeM, он знает только таблицу методов Kernel.

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

3 голосов
/ 11 февраля 2012

Это скорее обходной путь, чем решение, подходящее, если вы не хотите определять функцию на основном:

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?

3 голосов
/ 11 февраля 2012

Вы хотите 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 в глобальной области видимости, но помните, что это загрязнит целые числа и другие вещи с помощью ваших методов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...