我自己扩展了Kernel
,并且在实例方法Kernel#abort
的定义中,我调用了单例方法Kernel.abort
:
module Kernel
extend self
def abort
puts "Press ENTER to exit..."
gets
Kernel.abort
end
end
abort
当我致电Kernel#abort
时,方法定义中的Kernel.abort
调用似乎是指原始Kernel#abort
(扩展为Kernel.abort
)。
Ruby如何知道当我写Kernel.abort
时,我的意思是原始abort
方法,而不是我刚刚创建的方法?我如何以递归方式调用刚创建的新abort
方法?
答案 0 :(得分:4)
Kernel.abort
的定义是首先定义一个实例方法Kernel#abort
,然后使其成为一个带module_function
的单例方法。 (This is definitely the case in Rubinius;我无法在MRI源中找到它,但请参见下文。)module_function
makes a copy of the method.重新定义abort
时,重新定义实例方法,但不重新定义单例副本。
Object
包含Kernel
,因此当您说abort
时,您会获得已重新定义的实例方法,但当您说Kernel.abort
时,您会获得单身方法,你没有重新定义。
如果您真的想在abort
中使用递归,或者只是为了证明此解释是正确的,请在重新定义方法后调用module_function :abort
。单例方法将更新为与实例方法相同,两种方法都将递归。
请注意,您无需extend self
重新定义abort
的实例版本。由于Kernel
中已包含Object
,因此您只需重新定义所有对象的实例方法即可查看重新定义的版本。另一方面,如果Kernel
首先使用extend self
公开#abort
,我们可以重新定义它而不会出现任何复杂情况。
以下表明用户定义的纯Ruby方法缺少递归,即module_function
负责而本机方法不负责:
$ cat foo.rb
module Foo
def bar
puts "old version"
end
module_function :bar
end
module Foo
def bar
puts "new version"
Foo.bar
end
end
Object.include Foo
bar
$ ruby foo.rb
new version
old version
答案 1 :(得分:1)
你应该这样做:
module Kernel
class << self
alias :real_abort :abort
def abort
puts "press enter"
gets
puts "invoking real abort"
real_abort
end
end
end
IRB调用原始中止而不是您定义的abort
的原因是因为IRB repl有一个C语言变体的中止方法。
您可以在pry上执行show-source
[1] pry(main)> show-source abort
From: process.c (C Method):
Owner: Kernel
Visibility: private
Number of lines: 22
VALUE
rb_f_abort(int argc, const VALUE *argv)
{
rb_check_arity(argc, 0, 1);
if (argc == 0) {
if (!NIL_P(GET_THREAD()->errinfo)) {
ruby_error_print();
}
rb_exit(EXIT_FAILURE);
}
else {
VALUE args[2];
args[1] = args[0] = argv[0];
StringValue(args[0]);
rb_io_puts(1, args, rb_stderr);
args[0] = INT2NUM(EXIT_FAILURE);
rb_exc_raise(rb_class_new_instance(2, args, rb_eSystemExit));
}
UNREACHABLE;
}
在您提到的代码中,正在执行的中止不是Kernel.abort,而是从命令行调用它时映射到IRB的C语言中止
如果您希望屏蔽它,可以执行以下操作,以便IRB repl中的abort
执行重新定义的abort
def abort; Kernel.abort; end
然后,如果你运行abort
,它将调用你重新定义的内核的中止单例方法。