1

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
x-yuri
  • 16,722
  • 15
  • 114
  • 161

2 Answers2

1

I think you'll find part of the answer here: https://stackoverflow.com/a/57236134/6008847

Rubygems code replaces the included version of require.

When you call Kernel.require you get the original require method.

When you cal require you get the method from Rubygems (the receiver will be the context from where you call require).

gem_original_require is an alias for the original require method: https://github.com/ruby/ruby/blob/v2_6_3/lib/rubygems/core_ext/kernel_require.rb#L16

matthias_h
  • 11,356
  • 9
  • 22
  • 40
Gimmy
  • 51
  • 5
  • Your answer indeed clarifies some issues, but not all (or most) of them. See my updated question (after hr). And yes, receiver is what becomes `self` when you call a method. – x-yuri Apr 01 '20 at 00:33
  • I've been digging into singleton classes, method lookup, class/instance eval, the main object, you name it. Particularly I've answered this question. You can [check it out](https://stackoverflow.com/a/75077722/52499) if you want. It feels like I finally understood how it all works. Well, at least the class-related stuff. And a [couple](https://medium.com/@leo_hetsch/demystifying-singleton-classes-in-ruby-caf3fa4c9d91) of [related](https://gist.github.com/x-yuri/69c48ac5c3af0c1d3b11dc9a7f541f92) [links](https://stackoverflow.com/questions/917811/what-is-main-in-ruby/75066706#75066706). – x-yuri Jan 11 '23 at 02:32
0

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.

x-yuri
  • 16,722
  • 15
  • 114
  • 161