问题:kernel 中的 puts 方法是模块实例方法还是模块方法(是否存在模块实例方法和模块方法这种区别)?Kernel中有下面的一句话:
The Kernel instance methods are documented in class Object while the module methods are documented here.
是否说明本篇文档罗列的方法都是模块方法,比如说该文档中的 puts 方法,可以通过 kernel.puts 实现的,但是在文档左侧的列表中出现的标示符号都是"#"符号,这应该是模块实例方法的标示符,如果是模块方法,应该使用“::”,见文档所示。官方文档都不会出错,能不能帮忙指出我的理解错误。
Ruby 中有一些方法(如 print)可以随时随地进行调用。看起来就像所有的对象都有 print 方法一样。这是因为这些方法实际上都是 Kernel 模块的私有实例方法:
Kernel.private_instance_methods.grep(/^pr/) # => [:printf, :print, :proc]
这里的秘密在于 Object 类包含了 Kernel 模块,因此 Kernel 就进入了每个对象的祖先链。于是,无论哪个对象都可以随意调用 Kernel 模块的方法。这使得 print 看起来就像一个关键字,其实它只是一个方法而已。
-----摘自《Ruby 元编程第 2 版》
我自己有点钻牛角尖了,不多还是想把细节搞清楚,看了原书的内容。下面有几个小问题。 问题 1:任何对象都可以调用 Kernel 模块的方法,但是下面的 obj 对象调用会报错。
class KernelDemo
end
obj = KernelDemo.new
obj.puts("this is the Kernel method") #报错private method puts
问题 2:我构造了如下的代码,在模块中定义 private method, 并且 include 到类中,并且通过子类继承该类(类似于 Kernel、Object 和普通类的关系),但是在子类中调用 private method 会报错。
module KernelMdoule
def cc
puts "this is the private method"
end
private : cc
end
class FatherClass
include KernelModule
end
class SonClass < FatherClass
cc
end
问题 3:官方文档 中所有的方法都是标为 public instance method,但是通过在 irb 中输入如下 Kernel.private_instance_methods 可以得知本页中的所有方法都是 private instance method.而
Kernel.public_instance_methods.grep(/send/) #=>[:send, :public_send]
可以得知 send 方法是 Kernel 的公有实例方法,因为 Kernel 被引入到类 object 中,因此可以像普通公开实例方法一样被对象调用,而 puts 是私有实例方法,可以像关键词一样调用。如果是这样,那么文档中的表述是否有问题:
The Kernel instance methods are documented in class Object while the module methods are documented here. These methods are called without a receiver and thus can be called in functional form:
我试着解答一下:
问题 1:
obj.puts("this is the Kernel method") #报错 private method puts
puts 是 private instance method。private 的意思就是不能显示地指定 receiver object。你在这里显式指定 obj 为 receiver,当然会报错啦。
问题 2:
你这里笔误了吧。class SonClass < KernelClass
应该是 class SonClass < FatherClass
才对。
而且,你调用 cc
方法的时候,receiver 是 SonClass,而不是 SonClass 的实例,当然获取不到实例方法了。
问题 3: Ruby 文档的确有问题
#6 楼 @fighterleslie 非常感谢。问题二中代码的错误已经进行了修改。给出问题二的代码是为了和 Kernel 中的 puts 方法做比较,比如如下的代码:
puts "method one" #当前对象是main
class KernelMethod
puts "method two" #当前对象是class KernelMethod
def method_three
puts "method three" #当前对象是obj
end
end
obj = KernelMethod.new
obj.method_threee
结合上面的小伙伴的解释,补充了私有方法不能显式调用的知识,但是在类中进行调用 puts 方法(puts "method two"),与实例方法(puts 是 private instance method)只能被实例化对象调用相违背。在该例中,puts "method two"方法可以实现,但是在我给出的问题 2 中类似的方法是不能实现的。 我应该存在理解上的误区或者知识漏洞,感谢指正。
但是在类中进行调用 puts 方法(puts "method two"),与实例方法(puts 是 private instance method)只能被实例化对象调用相违背。
Good question. 你的疑惑是为什么在 KernelMethod
这个类上可以调用 puts
,因为 KernelMethod
这个类是 Class
类的实例对象(Ruby 中,一切值都是对象。)
KernelMethod.class # => Class
KernelMethod.instance_of?(Class) # => true
The Kernel module is included by class Object, so its methods are available in every Ruby object. The Kernel instance methods are documented in class Object while the module methods are documented here. These methods are called without a receiver and thus can be called in functional form:
你是感觉其中的the module methods
较为迷惑对吧?我猜测它说的是module_function
, 若这样的话,它干了两件事情:首先,把紧跟其后的方法都标注成了私有方法; 其次,让紧跟其后的方法可以单例方式访问。
例如:
print 'Hello world' # 毫无疑问,这样可以
Kernel.print 'Hello world' # 这也可以。但如果 print 只是Kernel里一个普通的 private 方法的话,是不能这样玩的。
#10 楼 @fighterleslie 感谢解答,您的解答我也考虑过,同理参考问题 2 中的例子:
module KernelMdoule
def cc
puts "this is the private method"
end
private : cc
end
class FatherClass
include KernelMdoule
end
class SonClass < FatherClass
cc
end
这样的写法会报错,原因为 undefined local variable or method,您的解释为:
“你调用 cc 方法的时候,receiver 是 SonClass,而不是 SonClass 的实例,当然获取不到实例方法了
但是其实 SonClass 也是一个对象,是 Class 的实例化对象:
puts SonClass.class #Class
puts SonClass.instance_of?(Class) #true
在我的理解里面,问题二中的代码
class SonClass < FatherClass
cc
end
和
class SonClass < FatherClass
puts “this is an private instance method ”
end
的实现形式是一致的,我在问题二的代码中也实现在 module 中构造私有实例方法,并且 include 到父类中,并且在子类中直接进行调用,这和 Object、Kernel 以及其他 Class 的实例化对象(即普通类)实现方式类似,但是实现的结果却是有错的,这是我的困惑所在,也是我的理解上的误区,能不能帮忙指正一下,感谢。
简单的说,虽然 puts
和 cc
都是 private method,但是 puts
是定义在 Kernel 模块中,cc
方法是定义在 KernelModule 模块中。Kernel
是一个特殊的模块,它和你自己定义的 KernelModule
模块有一些行为上的不同。
详细地说,这涉及两方面的知识:
module KernelModule
def cc
puts "this is the private method"
end
private : cc
end
class FatherClass
include KernelMdoule
end
class SonClass < FatherClass
cc
end
SonClass.ancestors # => [SonClass, FatherClass, KernelModule, Object, Kernel, BasicObject]
SonClass.class.ancestors # => [Class, Module, Object, Kernel, BasicObject]
Kernel
模块已经在 Object 模块中 include,而几乎所有的 Ruby 类都是直接或间接地继承自 Object
,所以 Kernel 模块会出现在所有类的 ancestors 列表中。当在 receiver 上调用 cc
方法时,Ruby 解释器会沿 receiver.class.ancestors 逐级查找是否有 cc
的定义,如果找到,会调用 cc
方法,同时不再向上查找;如果遍历 ancestors 而没有找到,则会在 receiver 上调用 method_missing 方法,一般情况下会抛出 NoMethodError 错误。
#12 楼 @qinfanpeng 认真看了 module_function 的方法解释,实现了您提到的两个功能,一是作为 private instance method 方法被调用,二是可以直接用模块名进行调用。但是 send 方法为何可以被 Kernel 模块名调用,也可以直接通过对象对用,如下代码:
Kernel.public_instance_methods.grep(/send/) # [:send, :public_send]
可以知道 send 方法是 Kernel 的 public instance method 方法,那么这里又是什么原理或者方法,使得其可以通过两种方式调用,即可以通过对象调用,也可以被 Kernel 调用。
另外请教前辈,这些知识点您是怎么积累的?
我想说说一点我的看法,我也是新手。
cc 方法是在 KernelModule 模块中的实例方法,include 进入 FatherClass 后
因为 Mix-in 的关系,会变成 FatherClass 的实例方法,SonClass 继承之后,成为子类的实例方法
而类中是不能直接调用实例方法的
所以,直接在类中 cc 会报错的
class SonClass < FatherClass
self.new.cc
end
是可以调用的,但是现在还有一个问题,就是在 module 中将 cc 声明为私有方法
所以,即使这样,调用 (访问) 还是失败的,去掉 private,就可以使用了
有一点要纠正的是,
但是其实 SonClass 也是一个对象,是 Class 的实例化对象:
这句话没问题,但是,cc 并不是 Class 中的实例方法,即使 SonClass 理解为 Class 类的实例对象,也不能调用此方法
而且,SonClass 并未继承 Class 类,其继承的是 Object 类,所以如果这样搞
class << Object
include KernelModule
end
就可以直接在类中 cc 了,当然 private 也是不行的
就像@fighterleslie 说的,从继承机制上可以看出来
最后,看了一眼帖子的时间,挖了一个坟帖,抱歉了
只是进行问题的交流,并无其他恶意