Подтвердить что ты не робот

Включение/расширение ядра не добавляет эти методы на главную: Object

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

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

4b9b3361

Ответ 1

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

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

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

module Kernel
  include Talk 
end

Затем Kernel будет внутренне наследовать include include includeTalk (класс со ссылкой на методы Talk).

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

Решение для вас probjem может состоять в том, чтобы расширить основной объект с помощью вашего нового модуля:

extend Talk

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

UPDATE

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

Я все еще немного смущен, почему мне нужно повторно включить Kernel в Object. В случаи, которые не связаны с основным объектом, я могу создать экземпляр объекта основанный на классе, а затем снова откроет этот класс и включит модуль, и этот объект увидит методы в моем модуле. Здесь что-то другое в том, как основной объект включает в себя ядро? я также не уверен, что вы подразумеваете под "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 не найдет свой метод экземпляра в своем одноэлементном классе и 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 в Kernel, таблица методов ядра не изменяется. Вместо этого ядро ​​теперь наследует includeM include class. includeKernel не видят методы includeM, потому что он не знает о цепочке наследования Kernel и includeM, он знает только таблицу методов Kernel.

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

Ответ 2

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

Ответ 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?