Search code examples
rubyrubygemsrequire

What's the difference between Kernel.require and Kernel.gem_original_require?


Consider the following script:

module Kernel
  unless defined?(gem_original_require_2)
    alias gem_original_require_2 require
    private :gem_original_require_2
  end

  def require(path)
      return gem_original_require_2(path)
  end
end

p method(:require)                  # #<Method: main.require>
p method(:require).owner            # Kernel
p method(:require).receiver         # main
p method(:require).source_location  # ["1.rb", 7]

puts '-' * 10
p Kernel.method(:require)                  # #<Method: Kernel.require>
p Kernel.method(:require).owner            # #<Class:Kernel>
p Kernel.method(:require).receiver         # Kernel
p Kernel.method(:require).source_location  # nil

puts '-' * 10
p Kernel.method(:gem_original_require)                  # #<Method: Kernel.gem_original_require(require)>
p Kernel.method(:gem_original_require).owner            # Kernel
p Kernel.method(:gem_original_require).receiver         # Kernel
p Kernel.method(:gem_original_require).source_location  # nil

puts '-' * 10
p Kernel.method(:gem_original_require_2)                  # #<Method: Kernel.gem_original_require_2(require)>
p Kernel.method(:gem_original_require_2).owner            # Kernel
p Kernel.method(:gem_original_require_2).receiver         # Kernel
p Kernel.method(:gem_original_require_2).source_location  # ["/home/yuri/.rubies/ruby-2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb", 34]

I've got a lot of questions about the output. Why is Kernel sometimes a class, sometimes a module? Why do they have different receivers? Does receiver become self when method gets called?

But more importantly, are Kernel.require and Kernel.gem_original_require the same method? Is there another place where Kernel.require gets overridden? If you can answer the rest of the questions, that would be awesome.


Let me put it another way. Let's try to reproduce this issue with run-of-the-mill classes and methods. As stated in the other question defining a method on Kernel creates 2 methods (instance and singleton). So:

class MyKernel
  # original require
  def require; puts 'require'; end
  def self.require; puts 'require'; end

  # copy original require
  alias gem_original_require require
  class << self
    alias gem_original_require require
  end
end

main = MyKernel.new

Kernel is in fact a module, but supposedly that doesn't matter here.

p MyKernel.method(:require)
  == MyKernel.method(:gem_original_require)
  # true

p main.method(:require)
  == main.method(:gem_original_require)
  # true

p main.method(:require)
  == MyKernel.method(:require)
  # false

So, supposedly to compare methods you've got to access them both either via a class, or via an instance. Let's override require:

class MyKernel
  # override one of the original require's
  def require; puts 'require'; end
end

Now we've got 3 requires:

main.require (original)
MyKernel.require
main.require

and 2 gem_original_requires:

main.gem_original_require
MyKernel.gem_original_require

We can compare like with like (instance methods with instance methods, singleton with singleton). But we no longer have access to the original main.require, so that leaves only singleton methods. And the following still holds:

p MyKernel.method(:require)
  == MyKernel.method(:gem_original_require)
  # true

But not in case of the real require:

p Kernel.method(:require)
  == Kernel.method(:gem_original_require)
  # false

Solution

  • To avoid ambiguity I'm going to call methods owned by singleton classes singleton methods, the rest are instance methods.

    class A
        def m; end       # instance method
        def self.m; end  # singleton method
    end
    

    (Although one might say that a singleton method is an instance method of the corresponding singleton class.)

    One might call singleton methods class methods, but that would be oversimplification. Objects can also have singleton methods:

    a = A.new
    def a.m2; end
    p a.singleton_methods(false).include? :m2                 # true
    p a.singleton_class.instance_methods(false).include? :m2  # true
    

    Instance methods are often called via an instance of a class/module (e.g. [].compact, compact is owned by the Array class). But not necessarily so (an example is one of the reasons which led to me creating the question). What adds to the confusion are the methods/instance_methods methods. A.methods returns singleton methods of the object A, and A.new.instance_methods... is unavailable. So generally you want to call methods on an instance of a class, and instance_methods on a class/module, but... that depends. For those willing to understand this better I suggest these two links.

    Now, as stated in the answer suggested by Gimmy, require is a global function. Which means that it's defined as both a Kernel's private instance method, and a Kernel's singleton method.

    $ ruby --disable-gems -e 'p [
      Kernel.private_instance_methods(false).include?(:require),
      Kernel.singleton_class.instance_methods(false).include?(:require),
      Kernel.instance_method(:require) == Kernel.singleton_class.instance_method(:require)
    ]'
    [true, true, false]
    

    Which means that you can call it basically from anywhere with require as an instance method (since almost all the objects are inherited from Object, and Kernel is included into Object):

    require 'time'
    

    Alternatively, you can call it (the other instance of the method) with Kernel.require as a singleton method:

    Kernel.require 'time'
    

    That, in its turn, means that rubygems create an alias of only the private instance method require.

    About the second part:

    class MyKernel
      # original require
      def require; puts 'require'; end       # (1)
      def self.require; puts 'require'; end  # (2)
    
      # copy original require
      alias gem_original_require require    # (3)
      class << self
        alias gem_original_require require  # (4)
      end
    end
    
    main = MyKernel.new
    
    class MyKernel
      # override one of the original require's
      def require; puts 'require'; end
    end
    
    p MyKernel.method(:require)
      == MyKernel.method(:gem_original_require)
      # true
    p Kernel.method(:require)
      == Kernel.method(:gem_original_require)
      # false
    

    r.method(:m) returns the method that would be called if you called r.m. As such MyKernel.method(:require) refers to the singleton method of the MyKernel class (2). And MyKernel.method(:gem_original_require) refers to its alias (4).

    In the second statement Kernel.method(:require) refers to the singleton method of the Kernel module. But Kernel.method(:gem_original_require) refers to an alias of the private instance method of the module Kernel. That is, the Kernel module itself doesn't have a singleton method gem_original_require, but since Kernel is also an object, and Object has a private instance method gem_original_require (included from the Kernel module), that is what is returned by Kernel.method(:gem_original_require):

    p \
      Kernel.singleton_class
        .instance_methods(false).include?(:gem_original_require),
        # false
      Kernel.singleton_class
        .private_instance_methods(false).include?(:gem_original_require),
        # false
      Kernel.class.ancestors,
        # [Module, Object, Kernel, BasicObject]
      Kernel.private_instance_methods(false).include?(:gem_original_require)
        # true
    

    To make it close enough to what happens with require:

    module Kernel
      def m; end
      private :m
      def self.m; end
      alias malias m
    end
    p Kernel.method(:m) == Kernel.method(:malias)  # false
    

    About the first part:

    def pmethod n, m
      puts n + ': ' + m.inspect
      puts '  owner: ' + m.owner.inspect
      puts '  receiver: ' + m.receiver.inspect
      puts '  source_location: ' + m.source_location.inspect
    end
    def hr
      puts '-' * 3
    end
    
    module Kernel
      unless defined?(gem_original_require_2)
        alias gem_original_require_2 require
        private :gem_original_require_2
      end
    
      def require(path)
          return gem_original_require_2(path)
      end
    end
    
    pmethod 'require', method(:require)
    pmethod 'Kernel.require', Kernel.method(:require)
    pmethod 'Kernel.gem_original_require', Kernel.method(:gem_original_require)
    pmethod 'Kernel.gem_original_require_2', Kernel.method(:gem_original_require_2)
    

    Let me first say a couple of words about the way ruby outputs objects:

    p Object      #=> Object
    p Object.new  #=> #<Object:0x000055572f38d2a0>
    p Kernel      #=> Kernel
    

    In case of singleton classes the output is of the form:

    #<Class:original_object>
    

    Given a singleton class S, original_object is an object such that original_object.singleton_class == S.

    p Kernel.singleton_class      #=> #<Class:Kernel>
    p Object.new.singleton_class  #=> #<Class:#<Object:0x000055cad6695428>>
    

    Instances of Method/UnboundMethod are output this way:

    #<Method: receiver(owner)#method(original_method)>
    

    r.method(:m) returns the method that would be called if you did r.m. r is the receiver, m is the method. Depending on the type of the method (singleton/instance) the receiver in the output means different things: for instance methods it's the class of the receiver, for singleton methods the receiver itself. owner is the class/module that owns the method (the class/module o such that o.instance_methods(false).include?(:m) == true):

    class A
      def m; end  # owner is A
                  # A.instance_methods(false).include?(:m) == true
      def self.m; end  # owner is the singleton class of A
                       # A.singleton_class
                       #  .instance_methods(false).include?(:m) == true
    end
    

    If an owner is a singleton class, its original object is displayed (an object o such that o.singleton_class == owner).

    When the receiver and the owner match, the owner is not duplicated in parenthesis.

    If the method is an alias, then the original method is specified in parenthesis.

    . is used in place of # for singleton methods (methods owned by singleton classes).

    Now the output should be self-explanatory:

    require: #<Method: Object(Kernel)#require>
      owner: Kernel
      receiver: main
      source_location: ["a.rb", 17]
    

    An instance method (owned by the Kernel module), the receiver is main (of the Object class).

    Kernel.require: #<Method: Kernel.require>
      owner: #<Class:Kernel>
      receiver: Kernel
      source_location: nil
    

    A singleton method (owned by the singleton class of the Kernel module), the receiver is Kernel. Considering that the owner is a singleton class, its original class (Kernal) is taken for the owner part. And since the resulting owner and receiver are both Kernel, the name is not duplicated.

    Kernel.gem_original_require: #<Method: Module(Kernel)#gem_original_require(require)>
      owner: Kernel
      receiver: Kernel
      source_location: nil
    Kernel.gem_original_require_2: #<Method: Module(Kernel)#gem_original_require_2(require)>
      owner: Kernel
      receiver: Kernel
      source_location: ["/usr/local/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb", 34]
    

    These two are aliases of the method require. They are instance methods, which means the originals are also instance methods. The receiver is Kernel (of the Module class). The owner is Kernel.

    To answer the rest of the question:

    Why is Kernel sometimes a class, sometimes a module?

    #<Class:Kernel> is the ruby's way of outputting a singleton class of Kernel.

    Why do they have different receivers?

    The receiver is the object whose method is being called. If the receiver is specified explicitly (e.g. Kernel.require), it's the object before the dot. In other cases it's self. And at the top level self is the main object.

    Does a receiver become self when a method gets called?

    Supposedly yes.

    are Kernel.require and Kernel.gem_original_require the same method?

    No, the first one is the Kernel's singleton method (an instance method of the Kernel's singleton class), the second one is a private instance method of the Kernel module.

    Is there another place where Kernel.require gets overridden?

    Not that I know of.

    So, supposedly to compare methods you've got to access them both either via a class, or via an instance. We can compare like with like (instance methods with instance methods, singleton with singleton).

    They must refer to the same instance of a method, and the receivers must match:

    class A
      def m; end
      alias ma m
    end
    class B < A; end
    a = A.new
    b = B.new
    p a.method(:m) == a.method(:ma)                   #=> true
    p a.method(:m) == b.method(:m)                    #=> false
      # receivers do not match
    p A.instance_method(:m) == B.instance_method(:m)  #=> false
      # receivers do not match
    p a.method(:m) == A.instance_method(:m)           #=> false
      # #<Method:...> can't be equal #<UnboundMethod:...>
    

    The issue with Kernel.method(:require) != Kernel.method(:gem_original_require) is that they belong to different classes/modules (Kernel.singleton_class and Kernel), although the receivers and method definitions match.