为什么我可以使用像 `puts` 这样的内核单例方法?
Why am I able to use Kernel singleton methods like `puts`?
在Ruby中,方法puts
是Kernel
模块的单例方法。
通常,当一个模块被另一个模块 include
d 或 extend
编辑时,该模块(但不是它的单例 class)被添加到继承树中。这有效地使模块的 instance 方法可用于模块或其单例 class(分别用于 include
和 extend
)。 . 但是混入模块的 单例 方法仍然无法访问,因为模块的单例 class 从未添加到继承树中。
那么为什么我可以使用 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 (#<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>
没有出现在祖先链中,方法查找如何找到这些方法?
相关:
- Ruby method lookup path for an object
- Class, Module, their eigenclasses, and method lookup
- 注意:这和我问的不一样,因为这是class继承,所以
#<Class:Class>
继承自#<Class:Module>
- Why a module's singleton method is not visible in downstream eigenclasses where it gets mixed?
"How does the method lookup find these methods at all if # doesn't show up in the ancestor chain?"
1.class.included_modules # => [Comparable, Kernel]
引用OP:
Kernel.instance_methods.grep(/puts/)
# []
正如您自己发现的那样,私有实例方法不会出现,它们会出现在 Kernel.private_instance_methods
。
你找错地方了。
像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#rand
, Kernel#readline
, Kernel#readlines
, Kernel#require
, Kernel#require_relative
, Kernel#select
, Kernel#set_trace_func
, Kernel#sleep
, Kernel#spawn
, Kernel#sprintf
, Kernel#srand
, Kernel#syscall
, Kernel#system
, Kernel#test
, Kernel#throw
, Kernel#trace_var
, Kernel#trap
, Kernel#untrace_var
, and Kernel#warn
这样的方法对它们的接收器没有任何用处。他们不调用私有方法,他们不访问实例变量,他们实际上完全忽略什么是self
。
因此,这样称呼它们会产生误导:
foo.puts 'Hello, World!'
因为 reader 会误以为 puts
用 foo
做某事,而事实上,它完全忽略了它。 (这适用于 尤其是 打印方法系列,因为还存在 IO#puts
和朋友,它们确实 关心他们的接收者.)
因此,为了防止您误导性地使用接收者调用这些方法,将它们设为 private
,这意味着它们只能在没有显式接收者的情况下调用。 (很明显,它们 仍然会 在 self
上被调用,但至少在视觉上不会那么明显。)
从技术上讲,这些根本不是真正的方法,它们的行为更像过程,但Ruby不没有程序,所以这是 "fake" 它们的最佳方式。
它们也定义为单例方法的原因是您仍然可以在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
,因为 Object
包括 Kernel
而 Kernel
是Module
的实例是 Object
的子 class,因此 Kernel
是其自身的间接实例。然而,方法 是 private
因此你会得到一个
NoMethodError: private method `puts' called for Kernel:Module
代替。因此,它们需要单独复制。实际上有一个辅助方法可以做到这一点:Module#module_function
. (This is also used for Math
,您可以在其中调用例如Math.sqrt(4)
或 include Math; sqrt(4)
。在这种情况下,您可以选择是否 include
ing Math
,而 Kernel
总是在 Object
之前 include
d。)
因此,总而言之:这些方法被复制为 private
实例 方法 Kernel
以及 public
singleton 方法(实际上只是 Kernel
的 singleton class 的 instance 方法) .它们被定义为 private
实例方法的原因是它们不能用显式接收器调用并且被迫看起来更像 过程 。它们被复制为 Kernel
的单例方法的原因是,只要显式接收器是 Kernel
,它们 就可以 在上下文中调用其中 Kernel
在继承层次结构中不可用。
看看这个:
#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
事实证明,答案是我问错了问题。 为什么我可以使用 puts
这样的内核单例方法? 答案是:你不能。
Kernel
的单例方法,与模块上的所有其他单例方法一样,是不可继承的。诀窍在于它们不是单例方法,本身...它们是模块函数。
在 ruby 中创建模块函数 创建方法的两个副本 : 一个单例方法 和一个 私有实例方法。这就是 Kernel.singleton_method(:puts)
和 Kernel.instance_method(:puts)
都有效的原因。
因此,因为 Object
include
s Kernel
,它可以访问其实例方法,包括 puts
.
我使用 #instance_methods
时出错,它只显示 public 个实例方法。要查看私人的,我需要使用 #private_instance_methods
,例如:
Kernel.private_instance_methods(false).grep(/puts/)
# [:puts]
在Ruby中,方法puts
是Kernel
模块的单例方法。
通常,当一个模块被另一个模块 include
d 或 extend
编辑时,该模块(但不是它的单例 class)被添加到继承树中。这有效地使模块的 instance 方法可用于模块或其单例 class(分别用于 include
和 extend
)。 . 但是混入模块的 单例 方法仍然无法访问,因为模块的单例 class 从未添加到继承树中。
那么为什么我可以使用 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 (#<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>
没有出现在祖先链中,方法查找如何找到这些方法?
相关:
- Ruby method lookup path for an object
- Class, Module, their eigenclasses, and method lookup
- 注意:这和我问的不一样,因为这是class继承,所以
#<Class:Class>
继承自#<Class:Module>
- 注意:这和我问的不一样,因为这是class继承,所以
- Why a module's singleton method is not visible in downstream eigenclasses where it gets mixed?
"How does the method lookup find these methods at all if # doesn't show up in the ancestor chain?"
1.class.included_modules # => [Comparable, Kernel]
引用OP:
Kernel.instance_methods.grep(/puts/)
# []
正如您自己发现的那样,私有实例方法不会出现,它们会出现在 Kernel.private_instance_methods
。
你找错地方了。
像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#rand
, Kernel#readline
, Kernel#readlines
, Kernel#require
, Kernel#require_relative
, Kernel#select
, Kernel#set_trace_func
, Kernel#sleep
, Kernel#spawn
, Kernel#sprintf
, Kernel#srand
, Kernel#syscall
, Kernel#system
, Kernel#test
, Kernel#throw
, Kernel#trace_var
, Kernel#trap
, Kernel#untrace_var
, and Kernel#warn
这样的方法对它们的接收器没有任何用处。他们不调用私有方法,他们不访问实例变量,他们实际上完全忽略什么是self
。
因此,这样称呼它们会产生误导:
foo.puts 'Hello, World!'
因为 reader 会误以为 puts
用 foo
做某事,而事实上,它完全忽略了它。 (这适用于 尤其是 打印方法系列,因为还存在 IO#puts
和朋友,它们确实 关心他们的接收者.)
因此,为了防止您误导性地使用接收者调用这些方法,将它们设为 private
,这意味着它们只能在没有显式接收者的情况下调用。 (很明显,它们 仍然会 在 self
上被调用,但至少在视觉上不会那么明显。)
从技术上讲,这些根本不是真正的方法,它们的行为更像过程,但Ruby不没有程序,所以这是 "fake" 它们的最佳方式。
它们也定义为单例方法的原因是您仍然可以在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
,因为 Object
包括 Kernel
而 Kernel
是Module
的实例是 Object
的子 class,因此 Kernel
是其自身的间接实例。然而,方法 是 private
因此你会得到一个
NoMethodError: private method `puts' called for Kernel:Module
代替。因此,它们需要单独复制。实际上有一个辅助方法可以做到这一点:Module#module_function
. (This is also used for Math
,您可以在其中调用例如Math.sqrt(4)
或 include Math; sqrt(4)
。在这种情况下,您可以选择是否 include
ing Math
,而 Kernel
总是在 Object
之前 include
d。)
因此,总而言之:这些方法被复制为 private
实例 方法 Kernel
以及 public
singleton 方法(实际上只是 Kernel
的 singleton class 的 instance 方法) .它们被定义为 private
实例方法的原因是它们不能用显式接收器调用并且被迫看起来更像 过程 。它们被复制为 Kernel
的单例方法的原因是,只要显式接收器是 Kernel
,它们 就可以 在上下文中调用其中 Kernel
在继承层次结构中不可用。
看看这个:
#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
事实证明,答案是我问错了问题。 为什么我可以使用 puts
这样的内核单例方法? 答案是:你不能。
Kernel
的单例方法,与模块上的所有其他单例方法一样,是不可继承的。诀窍在于它们不是单例方法,本身...它们是模块函数。
在 ruby 中创建模块函数 创建方法的两个副本 : 一个单例方法 和一个 私有实例方法。这就是 Kernel.singleton_method(:puts)
和 Kernel.instance_method(:puts)
都有效的原因。
因此,因为 Object
include
s Kernel
,它可以访问其实例方法,包括 puts
.
我使用 #instance_methods
时出错,它只显示 public 个实例方法。要查看私人的,我需要使用 #private_instance_methods
,例如:
Kernel.private_instance_methods(false).grep(/puts/)
# [:puts]