I'm trying to add a method into the Kernel
module, but instead of reopening the Kernel
and directly defining an instance method, I'm writing a module and I want Kernel
to extend/include
that module.
module Talk
def hello
puts "hello there"
end
end
module Kernel
extend Talk
end
When I run this in 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>
If I check the instance_methods
on Kernel
, I can see #hello was added to the Kernel
, but not in main Object
.
I've also tried using include
, but the same thing happens:
module Kernel
include Talk
end
However, if I define it directly:
module Kernel
def hello
puts "hello there"
end
end
Then it does get included in the main Object
.
$ hello
hello there
=> nil
Including the Talk
module in Object
works too:
class Object
include Talk
end
Perhaps I'm doing this wrong, or I'm missing something simple, but this behavior is confusing me.
I will try to explain it a little bit deeper:
when you include
module into some class, Ruby creates special internal include class and adds it to the hierarchy (please note that basically you are not allowed to see include class from Ruby programm, it is hidden class):
Given A inherits B
And we have a module C
When A includes C
Then A inherits includeC inherits B
If included module has other included Modules, then includeModules will be created for these modules as well:
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
Include class C method table is a link to method table of original class C.
When you extend
some object with a module, then this module is included into singleton class of this object, thus:
class << self; include C; end
# is the same as
extend C
Going to your example:
module Kernel
extend Talk
end
Here you include Talk
module into singleton class of Kernel
(Kernel
is an object of class Module
). This is why you can call hello
method only on Kernel object: Kernel.hello
.
If we write this:
module Kernel
include Talk
end
Then Kernel
will internally inherit include class includeTalk (class with link to Talk
methods).
But Kernel module is already included into Object
- Object
inherits its own includeKernel class and includeKernel class has link to method table of Kernel
and does not see methods of new include classes of Kernel
.
But now if you will re-include Kernel into Object, all Objects will see methods of 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
The solution for you probjem might be to extend main object with your new module:
extend Talk
Hope this is clarified a bit behaviour you observe :)
UPDATE
Will try to clarify your questions:
I'm still a bit confused why I have to re-include Kernel in Object. In cases that don't involve the main Object, I can instantiate an object based on a class and then later reopen that class and include a module, and that object will see methods in my module. Is there something different about how main Object includes the Kernel? I'm also not sure what you mean by "Object inherits its own includeKernel class and includeKernel class..." Why doesn't it see the new included module in Kernel?
You tell about the case with direct inclusion of module into class of an object:
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
in this case you will have object c
of class C
. Class C
inherits Object
(because it is an instance of Class
class. c looks for its instance methods in his singleton class -> then in his class C -> then in parents of class C (in this case Object
instance methods). When we include module M
into class C
, then includeM
will be a superclass of C
and if c
will not find his instance method in his singleton class and C
class, it will search for instance methods in includeM
. includeM
has a link to method table of M
class (instance of Module
class). Thus when c
search for instance method hi
it finds it in the M
module.
But this is different from case when you include module M
into Kernel
module.
At the program start Object
class includes module Kernel
: class Object; include Kernel; end
. This is why I say that Object
inherits from includeKernel
. includeKernel
has link to method table of Kernel
and when you change method table of Kernel, includeKernel
will also see these changes:
module Kernel
def hi # add hi method to method table of Kernel
puts 'hi'
end
end
hi # => hi # any Object now see method hi
But when you include module M into Kernel, then method table of Kernel is not changed. Instead Kernel will now inherit includeM
include class. includeKernel
don't see methods of includeM
because it does not know about inheritance chain of Kernel
and includeM
, it only knows the method table of Kernel
.
But when you re-include Kernel
into Object
, inclusion mechanism will see that Kernel
includes M
and will create includeM for Object
as well. Now Object
will inherit includeKernel
will inherit includeM
will inherit BasicObject
.
This is more a workaround than a solution, suitable if you don't want to define the function on 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
Incidentally, I wonder why the behaviour is different in this case. Shouldn't module_eval
have the same effect as module Talk; end
?
You want include
, not extend
.
include
adds the contents of the module as instance methods (like when you open a class or module normally); extend
adds them as class methods (so in your example, you could call Kernel.hello
, though this isn't what you want).
You might now be inclined to try this:
module Talk
def hello
puts "hello there"
end
end
module Kernel
include Talk
end
But this won't work either, because main
has already been instantiated, and include
simply alters the ancestry of Kernel
.
You could force main
to include your module at runtime, like this:
# (in global scope)
class << self
include Talk
end
This way, you're altering the metaclass of main
, not Kernel
(which would imply including it on a ton of other object you may not want).
If you do, you can also just do an include Talk
in global scope, but beware that this will pollute integers and other things with your methods.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With