Modules can be used for two things in Ruby: as namespaces for constants and as mixins.
The namespace usage is quite similar to C# namespaces: a module can contain constants (and all constants are contained in modules, even if you don't explicitly see it, in which case they belong to Object
) which are namespaced to that module. So, Foo::BAR
and Baz::BAR
are two different constants even though they have the same name.
Mixins are a bit trickier. One way to look at a mixin, is that a mixin is a class which doesn't know its superclass (or a class that is parameterized by its superclass). Okay, this sounds a bit convoluted. Let's look at an example.
Imagine, something like this were possible in C#:
interface IEachable<E> {
IEachable<E> Each(Action<E> block);
}
class Enumerable<S, E> : S where S : IEachable<E> {
List<T> Map(Func<E, T> block) {
var res = new List<T>();
this.Each(e => {†res.Add(block(e));});
return res;
}
}
So, you have a generic class Enumerable
which inherits from its type parameter S
(the superclass). This is actually a pretty accurate description of the Enumerable
mixin in Ruby. Everytime you instantiate the generic class into a concrete type, it ends with a different superclass.
So, the same class appears multiple times in the inheritance hierarchy, each time with a different superclass, but always just one.
This is different from multiple inheritance, where the same class appears once in the inheritance hierarchy, with multiple superclasses.
This property, that the class always has just one superclass, is called linearization and it solves a lot of the problems that traditional multiple inheritance has, which are usually related to the fact that there are multiple possible paths between two classes in the inheritance DAG, whereas with mixin linearization, there is just an inheritance tree, so there is always just one possible path.
In particular, this is what happens, when you mix a module M
into a class C
with superclass S
:
module M; end
class C < S
include M
end
When you call include
, Ruby will
- create a new class
M'
whose method table pointer, constant table pointer, class variable table pointer and instance variable table pointer point to M
's method table, constant table, class variable table and instance variable table
- set
M'
's superclass to C
's current superclass (i.e. S
)
- set
C
's superclass to M'
So that the inheritance hierarchy now looks like this:
C < M' < S
[Note: Actually include
delegates to append_features
, which really does the above work, and just like almost everything else in Ruby, you can actually alter this behavior by overriding append_features
, but that is advanced meta-magic.]
So, what are the practical implications of having a class that can be used in multiple places in the inheritance hierarchy? Well, you can use it to implement common behavior for unrelated classes.
Again, look at the Enumerable
mixin: it provides common behavior for any object that conforms to the following protocol: it must have an each
method that yield
s all elements one-by-one and returns self
. That's it. There doesn't need to be any sort of inheritance relationship between the classes of those objects. All that is required is that they respond to each
in an appropriate manner.
Which leads us to your question about interfaces: that's an interface. Although in Ruby, we usually call it protocol. Protocols in Ruby are very much like interfaces in C#, however, there is one crucial difference: there is no such thing as a protocol in Ruby. Protocols are latent, not manifest, they only exist in the programmer's head, in documentation, but not in the source code, and thus they cannot be checked by the language.