00:00:00.060
The next speaker is Chris Salzberg, and the title is "The Ruby Module Builder Pattern."
00:00:13.490
Hi everybody, my name is Chris Salzberg. I’m going to talk about module builders. What are the odds that Matt, the creator of Ruby, gets to talk about modules right before mine? I'm pretty happy about that.
00:00:27.150
So I'm a writer, programmer, and translator.
00:00:32.160
I'm known as Shioyama, and I have this gem called Mobility.
00:00:39.239
I'm going to talk a bit about that. I work at a company called Get Chica, and we have a booth in the sponsor area, so come talk to us if you're interested; we're hiring and based in Tokyo.
00:00:51.050
Okay, first, what I'm going to do is give a bit of an introduction. This is kind of the blog post that was the starting point for this talk.
00:00:56.969
It's a blog post I wrote on my blog a few months ago about modules.
00:01:02.219
It contains some fairly heavy Ruby concepts, and while I didn’t intend it to be a long thing, it ended up being quite extensive.
00:01:08.340
The article was about stuff I was working on in this gem called Mobility. I took about a month to write it, and after posting, I expected the Ruby diehards to find it interesting.
00:01:18.750
Surprisingly, it got picked up on Hacker News and other places and ended up getting about 30,000 to 40,000 views.
00:01:24.630
This surprised me because the content was relatively heavy.
00:01:31.619
Seeing this interest made me think it might be a good idea to take the topic further, so I proposed a talk at RubyKaigi.
00:01:37.710
Today, I'm going to take a little bit of a risk here. Instead of jumping straight into the blog post, which is heavy Ruby content, I'm going to take a few steps back and think about modules more generally.
00:01:44.430
I think it’s a good approach because Matt has set the stage with his whole talk about modules.
00:01:49.439
I want to consider modules outside of programming and apart from Ruby, and then see how much Ruby modules align with that idea.
00:01:56.189
So here's the idea of a module: programmers often have warm and fuzzy feelings about modules. Does anybody hate modules? Nobody hates modules.
00:02:03.570
But what the heck is a module anyway? Here’s one definition I found from Wiktionary: a module is a self-contained component of a system, often interchangeable, which has a well-defined interface to the other components.
00:02:10.259
This is a good definition because two key elements stand out: the self-contained part and the well-defined interface part. These will be important.
00:02:15.570
If we think about what an image of a module looks like, I did a lot of Google image searching before this talk.
00:02:22.380
To me, this image conveyed the feeling of a module. It has an interface with several connection points, indicating that it connects to other modular things. It is also self-contained.
00:02:29.280
This is actually a Russian space module. I know very little about space science.
00:02:34.740
In practice, we have lots of modules that connect together.
00:02:41.070
For example, my dad, an architecture professor, visited this interesting building in Tokyo called Naka Gin Tower.
00:02:47.370
It was built in the '70s and is very modular, allowing elements to be taken out and replaced.
00:02:52.950
Each unit inside the tower is self-contained and connected via a simple interface, which is just four screws. It's a bit scary but also fascinating.
00:03:06.480
Now, looking at another example, this motherboard for a Sony Playstation demonstrates modular design.
00:03:12.270
Here you have many different types of modules, each with its own interfaces, all self-contained. If they weren't self-contained, you wouldn't be able to get a game console out of this.
00:03:17.459
The idea of modular design aims to combine the advantages of standardization—which allows for cheap mass production—and customization, which can be costly.
00:03:23.880
Modular design is about encoding standardizable elements for mass production while allowing for some customization.
00:03:29.220
So, what elements contribute to modular design? First, you need an abstraction. This could be a living unit, a memory cell on a motherboard, or any common pattern.
00:03:34.980
Next, you define a well-defined interface. It's crucial to encapsulate the module and wrap it up behind this interface.
00:03:41.070
This is important because if you want to use a module in various contexts, you will face potential collisions with other things.
00:03:48.870
The broader you want your abstraction to be, the more different contexts you want to use your module in, necessitating a broader abstraction.
00:03:54.780
However, this requires your specification to be very expressive, capable of handling many types of interfaces.
00:04:02.010
You will also need tighter encapsulation because you will be using this module in many different places, and you won't know what kind of collisions may arise.
00:04:08.310
Now, that concludes my non-Ruby preamble, and I'll transition into Ruby. Matz already talked about many of these points.
00:04:14.100
In terms of modularity in Ruby, it’s viewed in many ways, but the first thing that comes to mind is the keyword 'module'.
00:04:20.670
Modules allow for modularity through inclusion. I listed here a collection of methods and constants.
00:04:26.760
As Matz pointed out, you can also use modules for namespacing, offering another dimension of functionality.
00:04:33.090
Modules also provide a callback method, specifically the included callback method.
00:04:39.120
Next, there's modularity through inheritance. We don't often think of this as modularity, but it is, as we reuse functionality.
00:04:44.130
You can subclass and share methods and constants. Again, there's a callback method for inherited modules.
00:04:49.950
In another sense, gems are also modules. You can try installing a gem called 'Dedica' if you have a console open.
00:04:55.920
It's a text adventure we created, and again, gems are modules—they're self-contained, with classes, modules, objects, and interfaces to their dependencies.
00:05:01.290
The key point here is that these gems are as flexible as their components allow.
00:05:07.470
For me, the starting point when thinking about modules was this gem, Mobility, I’m working on.
00:05:12.720
I won't dive into the details of the gem now, as it diverges from the topic.
00:05:18.300
The gem stores translations in a database, and abstraction is critical.
00:05:23.490
Many translation gems exist, each storing translations in a different way, such as in a JSON column or in another table.
00:05:29.190
Mobility abstracts all these storage strategies under one concept: backends.
00:05:36.570
Basically, it combines the functionality of five or ten other gems into one gem.
00:05:43.050
To achieve this, it abstracts these storage strategies into backend modules.
00:05:48.270
The gem then supports various plugins, many of which are built on this module builder concept that I’m going to discuss.
00:05:55.440
This was my motivation for exploring module builders.
00:06:02.010
There are wonderful aspects to Ruby modules, and I’m not going to criticize them.
00:06:08.310
But, they serve a basic purpose: I have my method, ‘foo’, and I include the module.
00:06:14.100
I can override it and perform method composition. That's acceptable.
00:06:20.670
However, are modules flexible? Let’s consider some pseudocode.
00:06:27.480
What if I want to use a module in a different context, passing some configuration into it so that it behaves differently?
00:06:35.550
If you attempt the given pseudocode, you’ll encounter errors, rendering this approach ineffective.
00:06:41.760
So the question arises, are modules encapsulated?
00:06:47.730
If a module defines a private method, and you include this module in a class, the private method should not be accessible.
00:06:54.490
However, that’s not how it works. If you include my module that defined ‘bar’, it overrides the original method.
00:06:59.390
What’s interesting here is that modules can create a sort of multiple inheritance situation.
00:07:04.100
Depending on how you use modules, if you're not careful, you can inherit many private methods—leading to a confusing mess.
00:07:11.430
So how do we address this issue? By harnessing the power of metaprogramming.
00:07:17.280
I love metaprogramming, and don't get me wrong—I’m not trashing it.
00:07:23.360
However, I’d challenge you to read and understand the code that represents metaprogramming—it can get convoluted.
00:07:30.500
In broad terms, metaprogramming often seeks to configure something in a module.
00:07:36.820
A lot of this stems from using a class and shoving the configuration elsewhere.
00:07:44.430
This approach piqued my curiosity, and as I dug into it, I concluded there has to be a better way.
00:07:50.500
Now I'll jump into the specific topic of my blog post about building with modules.
00:07:57.720
First, let's look at a small module called 'edible'. This module allows you to add coordinates.
00:08:04.050
It has a ‘plus’ method that takes a point object and creates a new instance with added coordinates.
00:08:10.370
I have a class called 'Point', which is a struct with X and Y coordinates. I include the module in my class.
00:08:16.060
When I perform the addition, I get the right coordinates, and it works.
00:08:21.680
Now, the issue is that including the 'edible' module requires accessors for X and Y, which limits the module's flexibility.
00:08:30.550
This limitation demonstrates the need for an expressive specification to support method composition.
00:08:37.460
The goal is to abstract a module so it can define addition based on any set of accessors, allowing for greater flexibility.
00:08:44.150
We can approach this with a concept called bootstrap methods. Let’s explore that.
00:08:50.360
We’ll define a method called 'define_adder', which accepts accessors and sets them up for any number of attributes.
00:08:56.970
The main point here is that we're now generalizing our method for multiple coordinate pairs.
00:09:05.670
Next, we can try creating a new class called 'LineItem', which has two accessors: 'amount' and 'tax'. This is different from our original 'Point' class.
00:09:14.090
If we include the previous version of the 'edible' module, it won’t work because it depends on 'x' and 'y'.
00:09:20.240
To overcome this limitation, we can extend 'define_adder' instead of including it.
00:09:27.920
By extending it, we can create configurable adders for the new class structure.
00:09:35.460
Now, when we call 'define_adder' with 'amount' and 'tax', we can see the expected flexibility in functionality.
00:09:42.320
We can add items and the math works as intended.
00:09:50.970
But how do we handle composition? If we redefine the method ‘plus’ with logging, we run into a problem.
00:09:57.680
The issue is that redefining it causes the new definition to clobber the old one, leading to inconsistencies.
00:10:06.040
In a class, methods don’t coexist: only one can exist per name—hence, redefining it leads to errors.
00:10:14.920
To resolve this, we'll need to redefine our methods on a module that’s included in the extending class.
00:10:20.890
Now, what we’ll do is create an anonymous module and define our 'plus' method there.
00:10:27.460
The beauty of this approach is that we can define methods inside the module while keeping them distinct.
00:10:34.990
Now, when you call the original 'plus' method from the class, it references the method in the ancestor module.
00:10:42.450
The assignment of methods is done in a way that respects previous definitions, avoiding errors.
00:10:49.590
Using anonymous modules enhances readability, and this method is frequently employed in Ruby frameworks like Rails.
00:10:58.550
This technique is visible in ActiveModel's attribute methods, which also employ anonymous modules.
00:11:05.150
When you create an anonymous module, include it, and define methods, the clarity becomes manifest.
00:11:13.170
Recapping, we explored three types of 'plus' methods: one with fixed accessors, one using a definer, and finally, using an anonymous module.
00:11:20.310
These examples illustrate the flexibility and expressiveness of the Builder pattern in Ruby modules.
00:11:28.340
In my blog post, I provide further details, but I won't dive into them here to keep the focus.
00:11:36.780
I looked at the example of attribute methods from Rails to show how they extend functionality through modules.
00:11:44.810
For instance, the module being extended defines methods via a bootstrap method, which adds to the system's modularity.
00:11:52.420
My concern is that this modular approach can lead to scattering of functionality across multiple locations.
00:12:00.970
The class has state, the module extends it, and this division raises a question about coherence.
00:12:06.290
I don't perceive this as truly modular since it can become difficult to track methods across differing blocks.
00:12:12.130
Now, let’s transition to talking about the module builder.
00:12:20.450
This diagram from a book on metaprogramming in Ruby outlines Ruby's core object model.
00:12:29.090
Here we see different objects, classes, and their subclass relationships.
00:12:35.970
An interesting observation I made while preparing my talk was how the relationships are depicted.
00:12:41.470
This diagram misses an important arrow, highlighting how modules relate to classes within Ruby.
00:12:47.690
Here’s the kicker: a capital 'M' module is a class itself.
00:12:57.950
While a small 'm' module can’t do everything a class can do, capital 'M' module certainly can.
00:13:04.900
Capital 'M' modules can have subclasses, which in turn can define their methods.
00:13:11.490
Inside a method of a subclass, I'm able to define that module's methods.
00:13:17.580
This might look strange but it’s essentially just combining familiar concepts in a new way.
00:13:24.390
This concept is what I term the 'module builder.'
00:13:29.920
I believe module builders offer significant advantages, as they encapsulate configurations into a cohesive unit.
00:13:35.390
This entails an initializer that allows you to configure the module.
00:13:42.660
In the initializer, you can define methods as necessary.
00:13:49.720
To substantiate this, consider the example from earlier, defining a plus method in the module.
00:13:56.780
This is a clean, concise way to define functionality without cumbersome modifications.
00:14:03.450
You can maintain organized and expressive code while including instances into classes clearly.
00:14:09.590
This emphasis on encapsulation leads to an intuitive understanding of your codebase.
00:14:17.100
Ultimately, using module builders enhances flexibility and ease of use.
00:14:23.730
Now, there are numerous ways to implement this design pattern throughout your Ruby projects.
00:14:29.390
For example, I recently discovered a gem called Alchemist.
00:14:36.360
The author cleverly utilized a module builder to structure their code, making it more maintainable.
00:14:43.520
With a clear initializer setup, the entire architecture became streamlined and readable.
00:14:52.030
Additionally, another gem, dry-equalizer, adopts similar principles for comparative analysis.
00:14:59.470
It showcases how useful module builders can provide succinct implementations without excessive boilerplate.
00:15:06.530
In conclusion, Mobility is a perfect context for implementing module builders.
00:15:13.620
It allows building backend strategies to store translations efficiently while maintaining modular and flexible configurations.
00:15:20.360
To facilitate those, I create module builders that customize module loading and execution on demand.
00:15:27.030
What surprised me most after posting the blog post is the reception toward the terminology 'module builder'.
00:15:34.140
I expected criticism, but instead, I found people more concerned with metaprogramming.
00:15:40.540
To me, metaprogramming represents one of Ruby's strongest features.
00:15:48.410
At its core, module builders are a simple twist on metaprogramming—it's just a subclass that’s configurable.
00:15:54.600
This design is straightforward, and I believe you can harness it in numerous ways.
00:16:01.840
So, I encourage you to explore the module builders concept, as it can be applied effectively across your Ruby projects. Thank you!
00:16:15.630
Sure, it's kind of specific, but in your examples of module builder, you were often including the module and then calling new on it, passing some parameters.
00:16:22.300
In your example with Mobility, you have this 'translates' keyword. I'm just wondering why there's this distinction.
00:16:33.860
Actually, it's mostly that Ruby developers find it confusing to include something that's an instance.
00:16:39.590
There's no technical reason; it's purely a syntax or human intuition issue.
00:16:46.190
The dry equalizer gem uses a different approach by defining 'equalizer' on the module directly.
00:16:50.780
The rationale is that people find this intuitive, even if there's no significant technical advantage to derive.
00:16:58.090
So, it’s purely a syntax issue.
00:17:04.930
Do we have any further questions? We have about five minutes left.
00:17:16.840
I understand you have a well-thought-out article detailing these concepts.
00:17:23.570
I'd be keen for you to translate this article into Japanese—it's a small request.
00:17:31.540
Thank you very much!
00:17:38.680
Would you like to have a few last remarks?
00:17:43.590
As a quick note, configuring modules can enhance capabilities significantly.
00:17:48.620
I'm glad to see the interest in this design approach.
00:17:54.440
Thank you for the engaging session, and I welcome any additional inquiries.
00:18:01.150
I could elaborate further if there’s enough interest.