为什么我可以使用诸如puts之类的内核单例方法?

rin*_*aun 6 ruby eigenclass

在Ruby中,该方法putsKernel模块的单例方法。

通常,当一个模块是另一个模块的included或extended时,该模块(而不是其单例类)将被添加到继承树中。这有效地使模块的实例方法可用于该模块或其单例类(分别用于includeextend)...但是混合模块的单例方法仍然不可访问,因为模块的单例类不是曾经添加到继承树中。

那为什么要使用puts(和其他内核单例方法)?

Kernel.singleton_methods(false)

# => [:caller_locations, :local_variables, :require, :require_relative, :autoload, :sprintf, :format, :Integer, :Float, :String, :Array, :Hash, :test, :warn, :autoload?, :fork, :binding, :exit, :raise, :fail, :global_variables, :__method__, :__callee__, :__dir__, :URI, :eval, :iterator?, :block_given?, :catch, :throw, :loop, :gets, :sleep, :proc, :lambda, :trace_var, :untrace_var, :at_exit, :load, :Rational, :select, :Complex, :syscall, :open, :printf, :print, :putc, :puts, :readline, :readlines, :`, :p, :system, :spawn, :exec, :exit!, :abort, :set_trace_func, :rand, :srand, :trap, :caller]
Run Code Online (Sandbox Code Playgroud)

请注意,这puts 似乎不是以下对象的实例方法Kernel

Kernel.instance_methods.grep(/puts/)

# []
Run Code Online (Sandbox Code Playgroud)

虽然Object包括Kernel

Object.included_modules

# [Kernel]
Run Code Online (Sandbox Code Playgroud)

据我所知,Kernel单例类(#<Class:Kernel>)不会出现在任何对象的祖先中。is_a?似乎对此表示同意:

Object.is_a? Class.singleton_class # false
Object.is_a? Kernel.singleton_class # false

Object.singleton_class.is_a? Class.singleton_class # true
Object.singleton_class.is_a? Kernel.singleton_class # false
Run Code Online (Sandbox Code Playgroud)

但是,由于某种原因,它们对于每个对象都显示为私有方法。

Object.puts "asdf"

# NoMethodError (private method `puts' called for Object:Class)
Run Code Online (Sandbox Code Playgroud)

如果#<Class:Kernel>祖先链中没有出现,方法查找将如何找到这些方法?

有关:

Jör*_*tag 5

您正在寻找错误的位置。

方法,如Kernel#ArrayKernel#ComplexKernel#FloatKernel#HashKernel#IntegerKernel#RationalKernel#StringKernel#__callee__Kernel#__dir__Kernel#__method__Kernel#`Kernel#abortKernel#at_exitKernel#autoloadKernel#autoload?Kernel#bindingKernel#block_given?Kernel#callccKernel#callerKernel#caller_locationsKernel#catchKernel#evalKernel#execKernel#exitKernel#exit!Kernel#failKernel#forkKernel#formatKernel#getsKernel#global_variablesKernel#initialize_cloneKernel#initialize_copyKernel#initialize_dupKernel#iterator?Kernel#lambdaKernel#loadKernel#local_variablesKernel#loopKernel#openKernel#pKernel#ppKernel#printKernel#printfKernel#procKernel#putcKernel#putsKernel#raiseKernel#randKernel#readlineKernel#readlinesKernel#requireKernel#require_relativeKernel#selectKernel#set_trace_funcKernel#sleepKernel#spawnKernel#sprintfKernel#srandKernel#syscallKernel#systemKernel#testKernel#throwKernel#trace_varKernel#trapKernel#untrace_var,并Kernel#warn没有做任何事情与他们的接收器有效载荷。他们不调用私有方法,不访问实例变量,实际上它们完全忽略了什么self

因此,如果您这样称呼他们,将会产生误导:

foo.puts 'Hello, World!'
Run Code Online (Sandbox Code Playgroud)

因为读者可能会误以为用来puts做某事foo,而实际上却完全忽略了它。(这适用特别是对的方法的印刷家庭,因为还存在着IO#puts和朋友,这的确他们的接收器服务。)

因此,为了防止您误以接收器调用这些方法,将它们制成private,这意味着只能在没有显式接收器的情况下调用它们。(显然,它们仍将被调用self,但至少在视觉上不会如此明显。)

从技术上来说,这些都不是真正的方法在所有的,他们的行为更像程序,但Ruby没有手续,所以这是“假”的最佳途径。

之所以将它们定义为单例方法,是因为您仍可以在Kernel不在继承层次结构中的上下文中调用它们,例如:

class Foo < BasicObject
  def works
    ::Kernel.puts 'Hello, World!'
  end

  def doesnt
    puts 'Hello, World!'
  end
end

f = Foo.new

f.works
# Hello, World!

f.doesnt
# NoMethodError: undefined method `puts' for #<Foo:0x00007f97cf918ed0>
Run Code Online (Sandbox Code Playgroud)

根本不需要单独定义它们的原因是实例方法版本为private。如果不是,则Kernel.puts无论如何您都可以调用,因为Objectinclude KernelKernelis的实例Module是的子类Object,因此Kernel是其本身的间接实例。但是,方法 private,因此您将获得

foo.puts 'Hello, World!'
Run Code Online (Sandbox Code Playgroud)

代替。因此,它们需要单独复制。实际上,有一个辅助方法可以做到这一点Module#module_function。(这也用于Math,在那里你可以调用例如Math.sqrt(4)include Math; sqrt(4)。在这种情况下,你的选择include荷兰国际集团Math或没有,而Kernel被预included中Object始终。)

因此,在总结:方法被复制为private 实例的方法Kernel以及public 的方法(这是真的只是实例方法Kernel单例类)。之所以将它们定义为private实例方法,是因为它们不能被显式的接收者调用,并且被迫看起来更像过程。将它们复制为的单例方法的原因是,只要在继承层次结构中不可用的上下文中,只要显式接收者为Kernel,就可以用显式接收者调用它们。KernelKernel

看一下这个:

class Foo < BasicObject
  def works
    ::Kernel.puts 'Hello, World!'
  end

  def doesnt
    puts 'Hello, World!'
  end
end

f = Foo.new

f.works
# Hello, World!

f.doesnt
# NoMethodError: undefined method `puts' for #<Foo:0x00007f97cf918ed0>
Run Code Online (Sandbox Code Playgroud)