在扩展自身的模块中的实例方法中调用单例方法

时间:2016-02-11 12:51:26

标签: ruby

我自己扩展了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方法?

2 个答案:

答案 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,它将调用你重新定义的内核的中止单例方法。