RubyKaigi 2017

The Ruby Module Builder Pattern

RubyKaigi2017
http://rubykaigi.org/2017/presentations/shioyama.html

Did you know that Ruby has configurable modules? One of the most interesting features of Ruby, the Module Builder Pattern is also probably its least well-known. Simply subclass the Module class, dynamically define some methods in an initializer, and boom, you can create named, customizable modules to include in other classes. In this talk, I'll explain how I've leveraged this unique feature of Ruby to build a translation gem called Mobility that can handle a wide range of different storage strategies through a single, uniform interface.

RubyKaigi 2017

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.