 And if you have any comments or questions and you want to contact me afterwards, I have my email up here. So I'm actually a geologist by education and as a geologist we study minerals. And assuming that we study minerals, that's why I'm here at RubyConf. But usually when I meet people and they tend to have this very romantic view of geology and that you're climbing these highest heights and grabbing some samples along the way, check out these awesome rocks or check out these awesome formations. But in reality it looks a lot like this. It's very exciting I know, looking under a microscope. And also in one particular area of geology you also tend to study a lot of these. Does anybody actually know what these are? Core samples, right? Exactly. And what they do is, if you study these enough, you get a nice little graph or chart that looks like this. It gives you an idea into the past environment of what's going on. And it might look something like this. So right now you're probably wondering, well, I'm at a Ruby conference, why are you talking about rocks? And that's because there is a very nice parallel between programming and what's going on in geology right here. If you were to only look at about five meters of this chart, you're not going to be able to see the whole picture. And the same thing in Ruby and metaprogramming and lambda functions, we have all these techniques we can use. But we tend to get lost in the trees and we can't see the forest. We can't really see what's going on. And hopefully I can help with that. And I want to start off by saying Ruby is beautiful, so our DSL should be beautiful. This is not a rip-off of many swans. It has a very ugly acronym, I promise. But I think that our DSLs, or Ruby in general is known for being a very human readable language. And I think our DSLs should live up to this reputation or even match this reputation of being readable yet extremely powerful. And this talk is going to go over some of the common ways developers go about using the dynamic nature of Ruby to achieve these DSLs. And I went through many popular libraries and I categorized or listed each time they would use metaprogramming or lambda functions. And then I categorized them even further into use cases or best practices. And the libraries that you're going to see here are representative of the other libraries that tended to use that same pattern. And when we design interfaces in Ruby, we tend to ask a few questions like who are we designing for or what do our users want out of our library? Well, they want to be able to use what it's meant for and they want it to be very easy to use. And that's not to say that the implementers love paying, it's just the Ruby way of making the user experience very happy. And one last stop before we move on. I want to talk about painting for a second. Everyone knows what the painting in the middle is, right? That's Starry Night by Van Gogh. But the question I want to pose to you is would this painting have been as famous or as influential if Van Gogh had used a style on the left, which is a Monet or the style on the right, which is a $300 million painting, believe it or not? And probably not, right? He used a very specific style for a very specific reason. And what you're going to see throughout this talk is that many of the patterns or the techniques that are used by different libraries were used to achieve a certain aesthetic design. It's not required. Your interface can actually be very ugly if you want it to. But that's not the Ruby way, is it? And the roadmap we're going to choose to go through this problem is we're going to talk about some goals that we'd like. And then we're going to talk about the problems associated with reaching those goals. And then we're going to look at the public interface that actually satisfies that. And then we're going to dive deep a little bit and look at the implementation. So code heavy, beware. And the first case we're going to talk about is trust issues. I don't mean to bring up any emotions with you guys, but developers tend to have trust issues, right? We tend to code defensively in such a way that we want to prevent others from making mistakes and from ourselves making those same mistakes, right? And one way we can approach this is through configurations, right? We want to be able to provide configuration options while handling quality assurance. In a way, you can do this is a singleton. That means every time that a user interacts with an object, it will have this global effect versus a very localized mess. And moving on from this, we can look at some typical ways that you would actually set up an object or library, right? Every time you interact with a library, there's always some interaction involved. And this is probably the simplest way you can do it. You have a config object, dot new, very explicit, very simple. It's very obvious what's going on, and you pass in five values. Now, one of the problems here is that five values is quite a lot to remember, and it could be even more depending on the amount of options that you provide. And I, as a user, don't want to have to flip back and forth between documentation every time I mess with this stuff. And not only that, we can't rely on defaults, because we have to pass in a value every time. And moving forward again, this actually solves both of those problems. You don't have to provide a value for every single configuration option, and you don't have to rely on order. But a problem that you run into here is that a user or someone else you're developing with can set these values up pretty much anywhere they want. So let's look at a way, before we review this, is we are just looking at providing configuration options, but still handling the quality assurance for the user, and Rails actually does this with config. Config sets up configurations for multiple environments. And all you do is you pass in a block, and you evaluate in that block this config variable that's passed in. And the magic that happens here is you have a config module, but in it, it's this yield self part. Yield self is actually the singleton class of the config. So now, every time that you operate on this variable in this block, it's actually operating on the singleton. So no matter where the user sets this up, or when this set this up, it's always going to revolve around the singleton. You're saving them from future mistakes. And here's another way, here's another version. This is actually a hash, and Geocoder actually takes addresses and converts them to longitude and latitude. And you just pass in this hash, but underneath, as we'll see, it gets passed to this instance. And this instance is really just a singleton, right? You're just passing in this hash, it merges these options onto the singleton. Again, it's a way of secretly forcing the user to use a certain interface. And you're probably wondering, well, which one should I use, right? If both of these satisfy our conditions, we have a block and a hash. I'm of the opinion that the block is more Ruby-like. You pass in an object, you work on that object, you do some stuff, right? But on the right, you're passing in a hash. And there's a lot of redundancy that doesn't really add to the intention of what's going on. So you have these colons, these hash rockets, these commas. It doesn't really add anything. But when you look at the config.setup on the left, every time you operate on that config variable, it's intentional as to what's going on. You're explicitly saying, okay, I have this object and I'm setting a configuration value on it. And moving on to case number two, which is situational language. I'm going to define, offering situational language. I'm going to define it as context-specific method names that rely on one implementation. We want a flexible interface for our library. So we may want to offer multiple method names, but they still rely on one single implementation. That's what I'm talking about. Here's a typical way you would create flexibility. It's very iterative. You define a path, or define a pinpath method. Passes in a path, and then it pushes that onto a pass variable. But if you have several paths, it makes sense to pass in an array. And then you smash up that array, and then you push it onto the pass variable. Now, the problem with, there really isn't anything wrong with this, is there, but it's repetitive. And it would be nice if we could actually get both of these things at the same time, and yet still offer this situational language. One implementation, and here's two libraries that do this. Hike allows you to find a file within a set of paths. And RSpec, obviously, testing framework. But if you see here, both of these instances actually, behind the scenes, rely on the same method implementation. So a pinpath and a pinpath relies on the same implementation. And so does a hash including and a string including. And this is not hike right here, this is, they actually offer a condensed version. And this is what it looks like. They define one method name. But then they alias that method name to say, hey, I want to give the user the opportunity, if they want to pass in one value and use a pinpath, if they're like, it makes more sense. It's context specific, it's situational language. And some common situations in which you're gonna see this, are singular versus plural method names, like we just saw, with path and path. And another example of this is synonyms with RSpec. So RSpec's alias matcher, alias is the method, and it does a few other extra stuff for them, documentation wise. And, but as you see here, you can actually use a string including on a hash, because it relies on the same implementation, but they're allowing you, or providing this flexibility to use it if you want to. Which, and one more option is RSpec has a very human, like readable interface. So it may make sense to make verb tenses different, satisfy versus satisfying, depending on the case. But one of the cons is cognitive load. Yeah, and you may feel, or if you're using this library, especially for the first time, that you may feel like you need to know all of this information, and it may feel overwhelming, but that's not really the intent of the developer. The intent is that it's there if you need it. And as you get used to it, it actually becomes very nice, as sort of a documentation tool as to what the object you're operating on, or the amount or number of stuff you're passing in. And case number three is going to be leveraging APIs. And so our goal is going to be leveraging the generally accepted meaning of existing APIs. What do I mean by this? Every time you use Ruby, you are using the public interface that Matt's created to solve problems. And in a way, you're inheriting his design. And so whenever you inherit code elsewhere, you're inheriting their design, no matter what. And you have to decide whether or not you're going to change the meaning of the design that you've inherited. Whether you're going to keep the generally accepted meaning, or if you're going to leverage it. And Polyglot is a library that allows you to load Ruby files with non-Ruby extensions, so it's a custom loader. And here's how they might do that. They open up the kernel module, they define a Polyglot require method. And they do some awesome Polyglot stuff, and then they wrap that around the require method to handle normal cases. So that in your code, it looks something like this. Polyglot require, stuff.weird. Again, they are adding to the existing API. Require means something to a developer, right? It has a generally accepted meaning within the Ruby community. But what they've done here is they've just added to it. They've just wrapped behavior around it. They didn't actually change what it means to require a file. And here's their implementation. Actually, this isn't around alias. What's going on here is that alias, the original require, with Polyglot original require. And then in their require method, they rely on the original implementation. And this is what it looks like. You can require stuff.weird with these strange non-Ruby extensions. But the older version is still directly available through Polyglot original require. And even though in Polyglot, they actually do handle normal use cases. So they actually did not change the meaning. And you're probably wondering, well, does this have some drawbacks? And the answer is yes. This was actually common in Rails for a long time. And you actually got it through something like this, alias method chain. And what it does is it takes the original method called foo. And this is gonna be the new method that doesn't have a certain feature. And foo is actually gonna take on some extra feature. And so that method is gonna be foo, and you do that through alias method chain. The problem is that if foo with feature relies at all on the original method name, you're gonna get infinite recursion. And not only that, you're gonna get extra method names in the namespace that you're probably not gonna use. And if alias method chain actually gets called twice, the original implementation is essentially cease to exist, you can't call it anymore. And this has actually been deprecated in Rails in favor of it being offered in Ruby. And you do that by defining some behavior in a module. So I have a method called foo, and it relies on some method that I am going to end up inheriting. But the magic is here, prepend. What it does is it takes module, new behavior, and it inserts it in the ancestor chain below my class. So now foo calls the foo, sorry, the foo in new behavior relies on the foo in my class. So now you don't have all these extra method names, and you can just rely on Ruby's object model to accomplish the same thing. And case number four, declarative DSLs. This is going to seem not meta program E at all. And our goal is actually going to be declarative DSLs for setting up classes in your domain. It's going to look like normal Ruby, and it should, we use these every day, it's called adder accessor, adder reader, adder writer. And it feels like it's part of the Ruby language, but it's not, it's just a method, right? It just feels like it is, because it fits so well within the domain of the problem that you're trying to solve. And not only that, but we want to automate some sort of common functionality for class setup, and we'll see what that means right here. Rails does this with Active Record Base. A user is a model, but your model can actually be anything, right, it can be a book, it can be a user, it can be anything, but what Active Record Base does is think of it like a platform, sort of like a Raspberry Pi, where you have this base that you build up on top of, to where you can actually have a T-brewer, or a video game emulator. Essentially, just anything you want, just take your pick. But that's what Active Record Base is doing, and then it offers optional functionality to build on top of that, so that when you interact with Active Record Base, you just say, okay, I want this, and then it executes that functionality. I don't care how it does it. We're very familiar with Hasmany, right? It's very common. And it's a very decorative statement, right? You say, I want this, and it gives you that. But there's a lot under the hood about what's going on. But it's also so simple at the same time, because class macros are just class methods. They're just executing a problem that feels seamless within the domain in which you're executing. Active Record Base deals with databases. So it feels natural that Hasmany should express a very natural one-to-many relationship. And it does that with belongs to, has one, hasn't belongs to many, several cases. And the implementation is actually not so difficult, right? It's just defining a Hasmany method way down here and up in the code. This is in the netters of Rails. And, but this functionality is defined on a singleton. It's that simple. It's just building up the language of your DSL. And anyone that inherits from your base class, like a framework like Rails does, that class now inherits and can use some of that functionality. And case five, domain switching. Our goal here is going to be defining behavior in a familiar domain so that you can execute that behavior elsewhere. And a better way to look at this is right here, Sinatra. So this helper's method is actually defined on the top level namespace. Which, but you can use this bar method very, very far away from that, right? You can use these in views. And this is what we're talking about. Underneath Sinatra, somehow it's allowing you to define behavior on this top level, somewhere familiar, and execute that elsewhere that is less familiar. You're making it easier on the user. It's the Ruby way, right? And every time you call a method on the top level, it gets sent to base, which gets sent to templates. But the magic is here, really. Class eval, that block that you send to helpers gets executed inside of some sort of context. Class eval, which is executed on base so that when your view calls render, you now have access to these methods so that each time you define these helper methods, you don't have to define them specifically in a view context. You can define them on a more familiar one in a top level domain. And opening doorways. We want to give the user, if we think of a library as a house, and as if you build a house, I've never built one, but if you would build a house, you would not put a door on the second story because the only way you can access is through a ladder. We want to allow many entries around the same structure of our library. We want to build a big, beautiful door. We want to allow people to easily enter. And here's clockwork. It executes applications at specific times. And here's a class that includes clockwork. And here's a way that you can access this manager of clockwork by scoping in and accessing it. But the problem is, actually, that's a little wrong. Sorry, it should be directly through the class. Anyways, what we want is to be able to execute manager, or access manager directly off the instance. We don't want to have to go through this roundabout way of accessing something that a user is going to commonly want to access. And here's Faraday. Faraday is an HTTP client library. So it makes sense that we're going to often be making Git requests. And here's a way you can do it. You have a Faraday module. You scope into this connection, you create a new connection, and you make a Git request. Google.com. But again, you're gonna be making Git requests all the time, so it should not be necessary that the user has to scope in to some sort of class in order to access it. We want something like this directly off the main module. And if you notice, in each of these examples, we're trying to avoid scoping in somewhere else within our namespace to execute. We want to make it friendly and easily accessible. And so again, this is where the mini entries comes from around the same structure of our library. And here's Clockwork. This is what happens when you include Clockwork. First, you can access it through the module itself, but you can also access it through the class and through the instance. It's pretty nifty. And here's how it works. Looks like a lot of code, but this is where it happens, really. There's an included hook so that whenever a class inherits Clockwork, that class comes into the hook and it includes the methods on the right. So we get the instance methods and then it extends those methods so we get the class methods. But also, Clockwork itself extends these methods. And that's where we get the instance method, the class method, and directly off the module itself. And if you look here, what we're actually doing is we're taking a singleton or a class method and we're trying to execute it in many different ways. So that now, everything we realize, everything still is around this manager. Everything still revolves around the Clockwork module. And here's Faraday. This is what we were just talking about earlier. So two different ways you can do the same thing. But they also offer a simpler version or here's how they get the simpler version is instead of creating a new connection every single time, we just want to say Faraday, get me this request and do it for me. And how it does that is there is a method missing on Faraday, Faraday module. So when you send a method there, that method then gets delegated to this new connection here. So every time that you send a request through method missing or executed method on Faraday, it's actually sent to this default connection. It creates a new connection for you. Again, this just is all about making the user happy. And case number seven, dynamic building. We want to be able to dynamically build objects with the broadest interface possible. I know it's a very broad definition, but here's an example. So when you build objects, this is an example or a way that you could do that. You have a library, you create an attribute called name, and then I add a value onto my name. I have to pass in that attribute every time if I'm gonna associate a value with it. And then I can yield some sort of value off of that. The problem with this, I mean, there really isn't any problem, it's pretty explicit. But if you're gonna have 10 or 20 of these things, it's gonna get pretty repetitive, right? And not only that, you're gonna have to associate each one of these attributes with every single value that you pass in. It's not dry. And the goal then, and this is where we're coming up from, we want to avoid the explicit method calls by mimicking Ruby. If we mimic Ruby, we can already leverage Ruby's existing interface, sorry, and which I think is good enough. And here's an example of that actually happening. Here's JBuilder and it revolves around this JSON object. And each time that you want to create a name, you send it a method call. And then you get something like this, right? You send in a block and then you send in a value. It just depends on what type of JSON object you're building up. And the internals look like this though. So every time you send a method call to this JSON object, it gets sent to method missing. And if there's a block, it executes a certain if statement, but if there's a value, it executes that. It's mimicking Ruby. You're just mimicking Ruby. It looks like a method call. And here's Paint. Paint manages terminal colors for you, but you can actually create your own schemes. And how you do that, and this is a pretty cool trick, I thought, kind of clever, you scope into the shortcuts constant and you create your scheme as a symbol name and then you pass in these symbols that are associated with some values. But now you can access your scheme like this as a module and as a singleton method. What happens behind the scenes is my scheme is now a module. It gets converted at runtime into a module. And here's Paint's implementation. It all has to do with const missing because your scheme does not exist until runtime. And when it's sent to const missing, it takes that module and it evaluates that module on Paint. So it creates a module for you at runtime. And not only that, but it takes each one of those symbols that you passed in as a hash and it makes some singleton methods. So now you can easily access your scheme like in the top right. But what is the pattern really? Why are these so easily able, why are we so easily able to understand what's going on even though they have probably the broadest interface possible. And that's because they look like Ruby. I know I've said that a million times but I really want to hammer that through. And we're just leveraging an existing interface to you for our own. We could have used explicit method names like create attribute, add value to that attribute but it's very redundant and we can just use Ruby. And this brings us full circle and we've examined several patterns that have to do with metaprogramming and lambda functions. So hopefully you've been able to see how these are actually used in real world examples like libraries and that they don't actually live in a vacuum. They achieve a certain interface and clean interface is at that but they also reduce redundancy. I've never heard someone say, man, I wish Sinatra made me write more code, right? It never happens. And but also another thing about these patterns, you shouldn't just wake up one day and think, okay, I'm going to use the observer pattern. It doesn't work like that, right? You wake up and you wait until you have that problem and later to rise organically and then you think, wow, I watched this guy, this geology guy, he's wearing all blue strangely enough and think about one of these patterns. And they say that you only remember the first, it's like the first five minutes and the last five minutes. So if there's one thing I want to leave you with, it's the situations, I just want to go through these situations again in which you should recognize and think, yes, this slide, this is what I want to remember. And you're going to start off with trust issues. If you're trying to force a certain type of public interface underneath and make sure that it's global and it's state, think singleton. And situational language, if you want to create a lot of flexibility for a certain context, think alias method. If you want to leverage existing APIs again around alias, don't know how much you're actually going to use that, but since we have module prepend now, but it's still out there. And declarative DSLs, think class macros, think I want to allow my user to use some sort of functionality that's predefined. And they just say, I want this to happen, and I provide that for them. Domain switching, allow your user to create some sort of functionality and a domain that's familiar for them, like the top level namespace, and then allow them to execute that elsewhere in a less familiar domain, like a few. And opening doorways, think hooks and method delegation, think about making your interface seamless in a way that they commonly interact around the namespace of your library. And lastly, dynamic building. If you ever want to build an object, it's probably the way you want to go. You're probably always going to use method missing and const missing. And that's it, questions.