Вызов метода 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
, он будет ссылаться на ваш переопределенный метод одноточечного прерывания ядра.