6

在 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]

请注意,这puts 似乎不是实例方法Kernel

Kernel.instance_methods.grep(/puts/)

# []

虽然Object包括Kernel

Object.included_modules

# [Kernel]

据我所知,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

然而,出于某种原因,它们显示为每个对象的私有方法。

Object.puts "asdf"

# NoMethodError (private method `puts' called for Object:Class)

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

有关的:

4

3 回答 3

5

你找错地方了。

方法如Kernel#Array, Kernel#Complex, Kernel#Float, Kernel#Hash, Kernel#Integer, Kernel#Rational, Kernel#String, Kernel#__callee__, Kernel#__dir__, Kernel#__method__, Kernel#`, Kernel#abort, Kernel#at_exit, Kernel#autoload, Kernel#autoload?, Kernel#binding, Kernel#block_given?, Kernel#callcc, Kernel#caller, Kernel#caller_locations, Kernel#catch, Kernel#eval, Kernel#exec, Kernel#exit, Kernel#exit!, Kernel#fail, Kernel#fork, , Kernel#format, Kernel#gets, Kernel#global_variables, Kernel#initialize_clone, Kernel#initialize_copy, Kernel#initialize_dup, Kernel#iterator?, Kernel#lambda, Kernel#load, Kernel#local_variables, Kernel#loop, Kernel#open, Kernel#p, Kernel#pp, Kernel#print, Kernel#printf, Kernel#proc, Kernel#putc, Kernel#puts, Kernel#raise, , , , , , , ,Kernel#randKernel#readlineKernel#readlinesKernel#requireKernel#require_relativeKernel#selectKernel#set_trace_funcKernel#sleepKernel#spawn, Kernel#sprintf, Kernel#srand, Kernel#syscall, Kernel#system, Kernel#test, Kernel#throw, Kernel#trace_var, Kernel#trap, Kernel#untrace_var, 并且Kernel#warn不要对他们的接收者做任何有用的事情。他们不调用私有方法,他们不访问实例变量,他们实际上完全忽略了什么self

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

foo.puts 'Hello, World!'

因为读者会被误导以为puts与 做某事foo,而事实上,它完全忽略了它。(这尤其适用于打印方法家族,因为也存在IO#puts和朋友,他们确实关心他们的接收者。)

因此,为了防止您误导性地使用接收器调用这些方法,它们是 made 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>

之所以需要单独定义它们,是因为实例方法版本是private. 如果它们不是,那么您无论如何都可以调用Kernel.puts,因为ObjectincludeKernelKernelModule的子类的实例Object,因此Kernel是其自身的间接实例。但是,这些方法 private,因此你会得到一个

NoMethodError: private method `puts' called for Kernel:Module

反而。因此,它们需要单独复制。实际上有一个辅助方法可以做到这一点:Module#module_function. (这也用于Math,您可以调用 egMath.sqrt(4)include Math; sqrt(4)。在这种情况下,您可以选择includeingMath或不,而Kernelis pre- includedObject始终存在。)

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

看一下这个:

#ruby --disable-gems --disable-did_you_mean -e'puts Kernel.private_instance_methods(false).sort'
Array
Complex
Float
Hash
Integer
Rational
String
__callee__
__dir__
__method__
`
abort
at_exit
autoload
autoload?
binding
block_given?
caller
caller_locations
catch
eval
exec
exit
exit!
fail
fork
format
gets
global_variables
initialize_clone
initialize_copy
initialize_dup
iterator?
lambda
load
local_variables
loop
open
p
pp
print
printf
proc
putc
puts
raise
rand
readline
readlines
require
require_relative
respond_to_missing?
select
set_trace_func
sleep
spawn
sprintf
srand
syscall
system
test
throw
trace_var
trap
untrace_var
warn
于 2019-09-10T16:46:09.283 回答
3

“如果 # 没有出现在祖先链中,方法查找如何找到这些方法?”

 1.class.included_modules #  => [Comparable, Kernel] 

引用 OP:

Kernel.instance_methods.grep(/puts/) # []

正如您自己发现的那样,私有实例方法不会出现,它们使用Kernel.private_instance_methods.

于 2019-09-10T15:08:23.817 回答
1

事实证明,答案是我问错了问题。为什么我可以使用内核单例方法puts答案是:你不能

Kernel的单例方法,与模块上的所有其他单例方法一样,是不可继承的。诀窍在于它们本身不是单例方法……它们是模块函数

在 ruby​​ 中创建一个模块函数会创建该方法的两个副本一个单例方法和一个私有实例方法。这就是为什么Kernel.singleton_method(:puts)两者Kernel.instance_method(:puts)都有效。

因此,因为Object includes Kernel,它可以访问其实例方法,包括puts.

我使用 犯了一个错误#instance_methods,它只显示公共实例方法。要查看私人的,我需要使用#private_instance_methods,例如:

Kernel.private_instance_methods(false).grep(/puts/)

# [:puts]
于 2019-09-10T16:48:39.667 回答