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 require
s:
main.require (original)
MyKernel.require
main.require
and 2 gem_original_require
s:
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
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
andKernel.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.