Search code examples
rubyclassincludeprogram-entry-point

Why does `include` behave differently at the top level?


I used the following hook to check the module that is doing the including when I do include Foo:

module Foo
  def self.included(includer)
    puts includer
  end
end

Module#include behaves differently in a module (where it is usually used) vs. at the top level. Inside a module, self is the module, which is an instance of Module. When I call include, the module doing the including is what self is:

module Bar
  puts self   # => Bar
  include Foo # => includer: Bar
end

At the top level of a ruby script, self is main, which is an instance of Object. When I call include at the top level, the module doing including is Object, the class of what self is:

puts self    # => main
include Foo  # => includer: Object

Can someone explain why?

The top-level object must be special; If I call to_s or inspect on it, it just says main, but if I create another object with Object.new and call to_s or inspect on it, I get the usual object notation: #<Object:0x007fae0a87ac48>.


Solution

  • main is special, and has its own definition of include. That is, its singleton_class has its own definition of include. To prove it:

    irb(main):017:0> method(:include).owner
    => #<Class:#<Object:0x007fc0398c6468>>
    irb(main):018:0> self.singleton_class
    => #<Class:#<Object:0x007fc0398c6468>>
    

    The include you're thinking of is defined on Module:

    MyClass.method(:include).owner
    => Module
    

    So, include acts different at the "top level", aka on the object we're calling main for the simplest possible reason: it's just a completely different method than Module#include.