Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a singleton method within an instance method in a module that extends itself

Tags:

ruby

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?

like image 828
Marko Avlijaš Avatar asked Feb 11 '16 12:02

Marko Avlijaš


2 Answers

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
like image 172
Dave Schweisguth Avatar answered Nov 11 '22 00:11

Dave Schweisguth


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.

like image 40
uday Avatar answered Nov 10 '22 23:11

uday