I extended Kernel by itself, and within the definition of the instance method Kernel#abort, I called the singleton method Kernel.abort:
module Kernel
extend self
def abort
puts "Press ENTER to exit..."
gets
Kernel.abort
end
end
abort
When I call Kernel#abort, it seems that the Kernel.abort call inside the method definition refers to the original Kernel#abort (extended as Kernel.abort).
How does Ruby know that when I write Kernel.abort, I mean the original abort method, not the one I just created? How would I recursively call the new abort method I just created?
Kernel.abort is defined by first defining an instance method Kernel#abort and then making it also a singleton method with module_function. (This is definitely the case in Rubinius; I couldn't find it in the MRI source but see below.) module_function makes a copy of the method. When you redefine abort you redefine the instance method but not the singleton copy.
Object includes Kernel, so when you say abort you get the instance method, which you've redefined, but when you say Kernel.abort you get the singleton method, which you haven't redefined.
If you really wanted to use recursion in abort, or just to demonstrate that this explanation is correct, call module_function :abort after redefining the method. The singleton method will be updated to be the same as the instance method and both methods will recurse.
Note that you didn't need to extend self to redefine the instance version of abort. Since Kernel is already included in Object, you only needed to redefine the instance method for all objects to see the redefined version. On the other hand, if Kernel had used extend self to expose #abort in the first place, we could redefine it without any complications.
The following demonstrates that the lack of recursion happens with user-defined, pure Ruby methods, i.e. that module_function is responsible and native methods are not:
$ 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
You should be doing something like this:
module Kernel
class << self
alias :real_abort :abort
def abort
puts "press enter"
gets
puts "invoking real abort"
real_abort
end
end
end
The reason IRB is invoking the original abort and not your defined abort is because the IRB repl has a abort method thats a C-language variant of it.
You can see it by doing 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;
}
In the code you mentioned the abort thats being executed is not Kernel.abort but the C-language abort thats mapped to IRB when you invoke it from commandline
If you want it to be masked you could do something like this so that the abort in IRB repl would execute your redefined abort
def abort; Kernel.abort; end
Then if you run abort it would invoke your redefined Kernel's abort singleton method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With