Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Ruby module inheritance work like class inheritance?

Tags:

ruby

Suppose I have a module called Flight with both class and instance methods. I can get its methods into a class using include, extend, or both:

class Bat < Mammal
  # Add Flight's class methods to Bat.
  extend Flight

  # Add Flight's instance methods to Bat.
  include Flight
  ...
end

include will add Flight to Bat.ancestors, but extend will not.

My question is, why is this different for modules than for classes? When I subclass Mammal, I always get both class and instance methods at once. However, when I mix in a module, I cannot get both class and instance methods at once (unless I use the self.included hook or something like ActiveSupport::Concern).

Is there a language-design issue behind this difference?

like image 807
Nathan Long Avatar asked Oct 16 '12 10:10

Nathan Long


2 Answers

I would like to address one part of your question:

include will add Flight to Bat.ancestors, but extend will not.

extend is not the same as include so it does something different obviously... You can think of extend being equal to an include on the class' metaclass.

Have a look at the following example:

module M
end

class A
  include M
end

# then you will see M within A's ancestors as you know
A.ancestors # => [A, M, Object...]


class B
  # the following is roughly the same as extend M:
  class <<self
    include M
  end
end

# then you will see M within B's metaclass' ancestors
MetaclassOfB = class <<B; self; end
MetaclassOfB.ancestors # => [M, Class, Module...]

So, since extend is like an include on the metaclass, you see the extended modules showing up in the metaclass' ancestor chain...

like image 24
severin Avatar answered Sep 23 '22 13:09

severin


Both Module#include and Object#extend are used to add the instance methods of a Module to an Object. Given the module:

module Flight
    def can_fly?
        true
    end
end

Module#include is used to add (or mix in) the instance methods of a module to the instance methods of a class or a module:

class Bat < Mammal
    include Flight
end

a = Bat.new()
a.can_fly?        # true

It actually affects the Object#is_a? method, so:

a.is_a? Flight     # true

Module#include is a private method, so it can only be called with function notation when defining a class or another module:

class Bat < Mammal
    self.include Flight     # NoMethodError: private method called
end

Object#extend adds the instance methods of a module as singleton methods to the object on which it's called, so you can do this:

b = Mammal.new()
b.extend Flight
b.can_fly?           # true
b.is_a? Flight       # true

c = Mammal.new()
c.can_fly?           # NoMethodError: undefined method

And only b will have the instance methods from Flight; other Mammal objects won't.

When calling Object#extend inside a class definition, the methods are added to the eigenclass of the class you're defining. This is the important difference between the two methods when using them inside a class definition, because the methods are added as class methods:

class Bat < Mammal
    extend Flight
end

Bat.can_fly?     # true

d = Bat.new
d.can_fly?       # NoMethodError: undefined method
like image 118
Alberto Moriconi Avatar answered Sep 25 '22 13:09

Alberto Moriconi