Вызов метода singleton в методе экземпляра в модуле, который расширяется

Я расширил Kernel сам по себе, и в определении метода экземпляра Kernel#abort я назвал метод singleton 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, который я только что создал?

Ответ 1

Kernel.abort определяется первым определением метода экземпляра Kernel#abort, а затем делает его также одноэлементным методом с module_function. (Это определенно имеет место в Rubinius, я не смог найти его в источнике MRI, но смотрю ниже.) module_function делает копию метода. Когда вы переопределяете abort, вы переопределяете метод экземпляра, но не одиночную копию.

Object включает Kernel, поэтому, когда вы говорите abort, вы получаете метод экземпляра, который вы переопределили, но когда вы говорите Kernel.abort, вы получаете метод singleton, который вы не переопределили.

Если вы действительно хотели использовать рекурсию в abort или просто чтобы продемонстрировать правильность этого объяснения, вызовите module_function :abort после переопределения метода. Метод singleton будет обновлен так же, как и метод экземпляра, и оба метода будут рекурсивно.

Обратите внимание, что вам не нужно 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

Ответ 2

Вы должны делать что-то вроде этого:

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 есть метод прерывания, который имеет вариант C-языка.

Вы можете увидеть это, выполнив show-source on pry

[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, но прерывание C-языка, отображаемое в IRB при вызове из командной строки

Если вы хотите, чтобы он был замаскирован, вы могли бы сделать что-то подобное, чтобы abort в реплике IRB выполнил ваш переопределенный abort

def abort; Kernel.abort; end

Затем, если вы запустите abort, он будет ссылаться на ваш переопределенный метод одноточечного прерывания ядра.