2

My understanding is that, when a module is included into a class, a copy of the module is made and put between the including class and its superclass. Let me quote from Ruby Under a Microscope by Pat Shaughnessy. Suppose that you have the following:

module Professor
end
class Mathematician < Person
   include Professor
end

When we run the above code, Ruby creates a copy of the RClass structure for the Professor module and uses it as the new superclass for Mathematician. Ruby's C source code refers to this copy of the module as an included class. The superclass of the new copy of Professor is set to the original superclass of Mathematician, which preserves the superclass, or ancestor chain.

Is it possible to get a reference to the included class? For example, I want to get a reference to the Kernel module included in the Object class.

Omar Khan
  • 412
  • 2
  • 12
  • Please provide an example to further clarify your intent. – Sagar Pandya Jul 13 '17 at 03:18
  • Why would re-opening `module Kernel` not suffice? – max pleaner Jul 13 '17 at 04:06
  • 1
    Well, there is [Module#included_modules](http://ruby-doc.org/core-2.4.0/Module.html#method-i-included_modules): `module M; end; module N; end; class C; include M; include N; end; C.included_modules #=> [N, M, Kernel]`. Is that what you are looking for? – Cary Swoveland Jul 13 '17 at 05:36
  • @CarySwoveland Your comment would be the (best) answer to this question. – sawa Jul 13 '17 at 05:37
  • @CarySwoveland, sorry no this is not what I am looking for. Please see edited question, though I have realized that I am probably asking something that is implementation dependent and therefore cannot have a general answer. – Omar Khan Jul 13 '17 at 08:41
  • I don't understand what Pat Shaughnessy means by, "...Ruby creates a copy of the RClass structure...". It doesn't imply that `include` creates a copy of the module, as subsequently removing an instance method from the module removes it from the class as well. To understand that one would have to know what "RClass" and "RClass structure" (C or C++ constructs?) mean. – Cary Swoveland Jul 13 '17 at 19:02
  • 1
    @CarySwoveland you are right it ( and here we are talking about MRI) does not make a *deep copy* of the module. I should probably edit my question again. But MRI does make a copy of the data structure (yes it is a C construct) representing the module and places it (by the manipulation of the *super* pointers inside the RClass structs) between the including class and its superclass thus giving you the ancestor chain when you call the `ancestors` method. The copy and the module share the same method table and therefore have the same methods even when you dynamically add methods. – Omar Khan Jul 14 '17 at 04:18

2 Answers2

3

My understanding is that, when a module is included into a class, a copy of the module is made and put between the including class and its superclass.

From a Ruby point of view, no copy is made. A reference is added to the ancestors, and that's it.

The behaviour described in your question (extracted from "Ruby under a microscope") is probably specific to CRuby. Internally, a Ruby Module is saved as a modified RClass, and a copy is made when the Module is included. It doesn't seem to be the case in Rubinius for example. I also don't think it's possible to access the copied, internal RClass referencing the included Module from Ruby.

You can check the Ruby behaviour this way :

module M
  def copy_or_reference?
    puts "copy!"
  end
end

class A
  include M
end

class B
  include M
end

m2 = A.included_modules.first
m2.send(:define_method, :copy_or_reference?) { puts "reference!" }

B.new.copy_or_reference?
#=> reference!

We extract M module from A's ancestors. We redefine the copy_or_reference? method in this ancestor module. If it were a copy of the original M module, M#copy_or_reference?, as called from a B instance, would still return "copy!".

To get the included modules, you could also use:

A.ancestors.grep_v(Class)

in Ruby 2.3+ or

A.ancestors.reject{ |o| o.is_a?(Class) }

in older versions.

If you want more information, you could try to ping @JörgWMittag.

Eric Duminil
  • 52,989
  • 9
  • 71
  • 124
  • 1
    Well this might be implementation dependent, but in MRI a copy is made of the Module (see my edited text for the question). The reason your code works the way it does is that the both the copy and the original maintain the reference to the same functions table. I suspect if it is implementation dependent there is probably no way to get a reference to the copy. – Omar Khan Jul 13 '17 at 08:11
  • 1
    @OmarKhan: I was going to update my answer to mention exactly what you wrote. "Ruby under a microscope" is a great book, but it's not always easy to follow. – Eric Duminil Jul 13 '17 at 08:12
  • I knew about `grep_v` but have never seen it used. – Cary Swoveland Jul 14 '17 at 05:17
1

Lets look at this with a sample:

module Bar
end

class Foo
  include Bar
end

p Foo.ancestors
p Foo.included_modules

This will print

[Foo, Bar, Object, Kernel, BasicObject]
[Bar, Kernel]

Object and BasicObject are in the ancestor hierarchy because, even without specifying it, Foo extends Object (and Object extends 'BasicObject', at least since Ruby 1.9+ where BasicObject was introduced).

Bar and Kernel are included Modules because Foo includes Bar directly and Object includes Kernel.

But the Module is not copied. It is simply referenced and used when looking up methods.

You can find some more details here: http://ruby-doc.com/docs/ProgrammingRuby/html/tut_modules.html

Pascal
  • 8,464
  • 1
  • 20
  • 31