Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Including/Extending the Kernel doesn't add those methods on main:Object

Tags:

ruby

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.

like image 209
Jack Chu Avatar asked Feb 11 '12 00:02

Jack Chu


3 Answers

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.

like image 178
Aliaksei Kliuchnikau Avatar answered Oct 25 '22 10:10

Aliaksei Kliuchnikau


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?

like image 4
user2398029 Avatar answered Oct 25 '22 10:10

user2398029


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.

like image 3
Jeremy Roman Avatar answered Oct 25 '22 09:10

Jeremy Roman