By

Rediscovering Modularity in Ruby

Our dev team at Degica headed down to Hiroshima last week to join the world’s largest gathering of Ruby developers at RubyKaigi 2017. It wasn’t the first time we attended the conference, but it was the first time we really put a coordinated effort into planning and preparing for the event. In all five of us were there, four developers and our CEO. We brought along some cute stickers, printed up some snazzy handouts, organized a little booth, and even designed a little text adventure game.

Talking at RubyKaigi

My main contribution to the effort was a talk I gave on the second day titled “The Ruby Module Builder Pattern”, based on a blog post I wrote recently. The blog post is a detailed explanation of a technique for building named modules in Ruby that can be customized and configured at runtime.

This was not the first time that I gave a presentation on programming, but it was the first time that I presented on a programming concept. Because that’s what the “Module Builder” is, it’s really a concept. And as a concept, it’s quite a simple one; it’s also very unintuitive.

I happen to like concepts that are simple but unintuitive, because they are eye-openers for everyone, even experts. Their significance also tends to be underestimated.

Matz, Me, and Modules

It just so happened that my talk was in a parallel slot with two other talks that all followed a morning keynote by Matz, the creator of Ruby. This alone was nice for me (guaranteeing that all those hungover Rubyists wouldn’t sleep in and miss my presentation), but the real impact of his talk on mine became clear with its title: “The Many Faces of Module”.

So Matz, the creator of Ruby, was talking about modules like me. You can imagine my surprise.

Now, the fact that the creator of Ruby was giving a talk similar to mine was, on the one hand, reassuring. It confirmed that Matz, like me, recognized the importance of modules to the language he designed. If great minds think alike, then I guess I’m a great mind too, right? (Ha ha.)

But to be honest, it was also more than a little bit unnerving. Watching his presentation, I felt that at any moment he would pull out a slide which would give away the punchline to my talk before I even had a chance to give it. If that happened, what would I do for the 40 minutes of my time slot? I’d be stuck standing up on stage looking like, well, an idiot.

Of course, that’s not what happened. Matz talked about the history of modules in Ruby, about all the things that they do, and some of the new things they might do. It was a talk about modules as mixins, modules as namespaces, modules as singletons. But it was not about building modules.

There’s another way, though, that Matz’ talk was different to mine.

Matz talked about the past, and he talked about the future. He talked about the past in terms of the inspirations that led to the creation of Ruby, and about how Ruby modules ended up they way they are today. He talked about the present in terms of new features that have recently been added, and about the future in terms of potential new features that may soon be added.

Now, I think Ruby is a great language. Obviously I do, or else why would I be programming in it every day? But watching Matz’ talk, I somehow felt that this legendary figure, the creator of a programming language used by thousands of people around the world, was focusing on the wrong thing. He was telling the story of a part of Ruby that many take for granted—modules—and this much was great. But ultimately, the bulk of his talk was a list of things his language was built to do.

Building for Surprise

One of the amazing things about programming languages is that, like human languages, you can build things out of them that go beyond their base grammar to do things that are unexpected and even surprising. I learned about this many years ago while studying a very different language—the language of cellular automata—which has this property in abundance.

Not all languages are equal in this respect. Ruby is known for being “malleable”, for the freedom it grants the programmer to twist and shape it to do things that would be impossible in many other languages. In this sense it can be a kind of “language-building language”, a product of it’s creator’s love of languages (which Matz emphasized in his talk).

But there’s another factor contributing to Ruby’s flexibility, and this is its object model. Matz did touch on this a bit, but I think he could have said more.

What to me is fascinating about this object model is its simplicity: whereas Ruby as a language can sometimes feel like a grab-bag of things borrowed from different places—a bit of Perl here, a bit of Smalltalk there—the model at its core is remarkably uniform and consistent. Indeed, seen through the lens of its object model, many parts of Ruby that seem disjointed and incoherent suddenly make much more sense.

The diagram above is from a book called “Metaprogramming Ruby” by Paolo Perrotta, which I highly recommend to anyone seriously interested in learning about the fundamentals of Ruby. The diagram shows some of Ruby’s core objects—Object, Class, Module, and a class MyClass with some instances obj1, obj2 and obj3—and the relationships between them. Two types of relationships are shown: the class of each object, and the superclass of each class.

The interesting thing about this diagram is that, in its original form in the book, there is one arrow that is notably missing, which I have added in red. The missing arrow is the one indicating that the class of Module is Class. Which is to say, (big-M) Module is a class. This is fundamental to the object model; nothing holds together without it.

Now Matz, in his talk, said (almost verbatim), “a module is not a class”. So when I came to the slide above in my talk, I had to defend it a bit. I pointed out that in his talk, Matz was talking about instances of the Module class, which (he is correct) are not classes themselves. But “big-M Module” and “small-m module” mean very different things in Ruby.

The Object Model Revisited

So why all the fuss about whether “module” is a class? Well, I mentioned a punchline in my talk earlier, that I was worried Matz would steal from me. That punchline is the slide below, and the “Module is a class” line is its lead-in.

The four points in this slide are each unsurprising things that can be said about Ruby’s object model:

  • A Module is a class
  • A class can have subclasses
  • (Sub)classes can define methods
  • Modules can also define methods

What is interesting here is that while these four things, taken each on their own, are simply properties of Ruby’s object model, when brought together into a single class what emerges is something new and powerful. This is the concept I call the “Module Builder”, a Module subclass whose methods can be configured at runtime.

Below is the example I used in the RubyKaigi talk and in my blog post. The class AdderBuilder builds modules that implement addition on a set of accessors (the keys in the initializer below):

class AdderBuilder < Module
  def initialize(*keys)
    define_method :+ do |other|
      self.class.new(
        *(keys.map { |key| send(key) + other.send(key) })
      )
    end
  end
end

Point = Struct.new(:x, :y)
Point.include AdderBuilder.new(:x, :y)

p1 = Point.new(2, 3)
p2 = Point.new(1, 2)
p1 + p2
#=> #<Point:.. @x=3, @y=5>

The real power, of course, is in the builder’s flexibility. Since it creates modules for any set of accessors, AdderBuilder can be used in many different contexts:

LineItem = Struct.new(:amount, :tax)
LineItem.include AdderBuilder.new(:amount, :tax)

l1 = LineItem.new(9.99, 1.50)
l2 = LineItem.new(15.99, 2.40)
l1 + l2
#=> #<LineItem:.. @amount=25.98, @tax=3.9>

I go into much more detail about this idea in my blog post. I’ve used the module builder pattern extensively in a gem I’ve developed called Mobility to build flexible, well-encapsulated abstractions; have a look if you’re interested in learning more about what they can do “in the wild”.

The New in the Old

What is amazing to me about the module builder is that while it is a fairly new concept to most Rubyists, it is not in fact “new” in any other sense; the whole concept falls right out of Ruby’s object model, dating as far back as module itself. No new language feature is required to make it work.

And this is where I feel that Matz—and many Rubyists who look to him for inspiration—are focusing on the wrong thing when they reach for new things to add to the language.

Let’s face it, Ruby is not young anymore. Its creator is growing some grey hairs. Many of us Rubyists are growing some too. Like buying a sports car to feel young again, it is tempting to add features to a language to make it feel new again. (Types seem to be particularly popular right now.) Sometimes this may be appropriate, sometimes not.

But the point that I want to make here is that Ruby has so many secrets hidden up its sleeves already that most of us don’t even know about. These are things that emerge out of the language’s core object model, which is flexible precisely because it is so simple.

So my message here—the message I delivered at RubyKaigi—is to take another look at this language we all love. Take a good look, and get to know it well.

Because some of the most interesting features of a language are not the ones that are cleverly designed, but the ones that emerge out of a clever design. And while we may not need more of the former, we can always do with more of the latter.

Looking for a Rails developer
Degica is looking for a Rails developer to work with our dev team. Checkout the link for more information.