 you get to a solution. Maybe it might take you a few hours, maybe a few days, but eventually, you will get to a solution. These problems are called obvious. Obvious doesn't mean I will know straight away. It means if I sit down, then I will know. Some problems don't work like that, so you don't really know how long you will take to get to a solution. You might take weeks. You might have a few false starts. And those are non-obvious problems. And then there are the problems that are harder than either obvious or non-obvious. And these other problems, well, you don't even know whether there is a solution. Maybe there is a solution, but it's not optimal. Maybe you have trade-offs. Maybe you have to try something different and see what happens. You have to experiment. Maybe you can try a few different things and just settle for the best that you find. And those are the problems that some people call deep. And I'm going to talk about a deep problem today. But first, let's talk about refinements. It's a feature introduced in Ruby 2. You probably know about them. In case you want the big picture, let's look at what refinements are for. What is the problems that refinements want to solve? So why do we have them? Well, in Ruby, you can do this. Most of us know. You can reopen a class that already exists and do stuff inside it. For example, define a new method or redefine a method that already exists. Here I defined a new method, shout, on strings, and now all strings have this method. Some people call these open classes or dynamic class scope. But most of us just call it with this vaguely negative term. We call it monkey patching. What is monkey patching for? Which is not an obvious question because if you show monkey patching to somebody who works, I don't know, with Java, they will think it's crazy. So what are the use cases for it? I can think of a few use cases, probably so can you, like five or six. But there are maybe three use cases that are very common. I will go for the classic examples. One is DSLs. Once again, textbook example from RSpec. Not the latest version of RSpec, an older version. Here we have something that is meant for testing. I want to test integers. So I'm saying, I'm describing fixed nums, the class for integers. They can be added here, two plus two should equal four. And the interesting side of this is that it looks so nice. It reads like it's a language that was designed for testing, but it's not, it's just a Ruby. So that's why domain specific language. And if you look at this, you cannot do this without monkey patching. In this particular case, for example, should is a method. So somebody had to add this method to at least two integers and probably to any object. So this is one use case of something that you can only do because of monkey patching. There is another one that is maybe less compelling, but at least as common, which is once again, textbook example, convenience methods. I imported the library and thanks to this library, I have these beautiful little methods like hours and minutes and I can do time calculations and they look really smooth. And in the Ruby world, we pride ourselves on writing code that is beautiful and this is beautiful. It's not like it's necessary, okay? I can get away with having just normal functions there, hours, which take a number, but this looks nice and looking nice is important for us. Then there is another use case which is usually not mentioned, but actually I feel it's important because it's so common and it's method wrappers. The idea here is that instead of just replacing a method, I'm adding more code around an existing method. In this case, I'm renaming the method first then monkey patching it. The mechanics are not important. The important part is the fact that I can wrap code around the length method in strings. So now when I called length, I'm relying on the previous length to do something else. Incidentally, this also shows why monkey patching is evil and dangerous because if you run this code, then you're in the context of a larger program, then your program has just a few milliseconds to leave because you just broke Ruby because everybody's relying on the length method in strings to do exactly what it does now, and if you change that, then you're dead, essentially, which is okay in this case because it's pretty obvious, but in a large system, it might happen and you don't even notice. It might happen and you don't even have control over it. Maybe you just, I don't know, required two libraries that monkey patch is the same method on the same class. So they step over each other and unfortunately these things do happen. And the problem with this is that they are global, right? Monkey patches are global. We don't like global stuff. It brings bad luck in programming. So we would like local monkey patches instead. And that's what refinements are, local monkey patches. So let's see how they are, not how they are implemented today, but what the original idea for refinements was. A refinement is very simple, a two-steps process. You define the refinement, you use the refinement. Define it. You need a module. Modules are used for a lot of things. In Ruby, they are used for mixins to add methods to your chain of ancestors. They are used for namespaces to organize your constants. Now they are also used as homes for refinements. So this module will carry the refinement. And the refinement itself is in here. You say, I want to refine a string. It's a method. And you pass a block to this method. And inside the block, you do exactly whatever you could do with a monkey patch. For example, you say, let's add the method short. That's it about defining the refinement. There is essentially nothing else to know. Or very little else to know. Then you use it. Let's say that you want to use the refinement in here. I gave this module a very long name just to avoid confusion with the module that is containing the refinement. To use the refinement in here, you use the method using. And then the refinement is active. And now you can use it. That's essentially all there is to know about using a refinement. A few details. The refinement is active from right after the using to the end of the module. The module can be a class. A class is just a module. There is also another use case. You might decide to use using at the top level of your file instead of inside the modular class. If you do this, then the refinement is gonna be active from right after the using to the end of the file. And that's it. Apparently, we are done with refinements. But there is more than meets the eye here. There is some weird corner case that then kinda explodes in your hands. Let's look at that. We define the refinement, recapping. We use the refinement in this case inside the class. Now we said that in our Ruby, you can re-open a class, right? You have this dynamic thing. So what happens if I re-open the class? In here, I'm inside the same class and there is a refinement in there. Will the refinement work? Simple question, right? I mean, it's in there. A slightly more complex case. What if I inherit from the class instead? Once again, it's kinda like the same scope, right? The scope of these kinda like the scope of C. Will the refinement work? And one more case. Because Ruby allows you to do awesome stuff with scopes. You can re-open scopes in many different ways. And one way that is quite common is by using a method that is called class evolve or module evolve. It's actually the same method. If I use class evolve to get back into the scope of the class, will the refinement work? Doesn't look like much. It looks like a corner case, but let's assume it does. Because in the very beginning, at the original idea, as the original idea or refinement went, yes, it does work. It does work, which makes sense. I mean, it's a scope, right? I get out of the scope. I get back into the scope. I find all the stuff that I left in there, like class instance variables, instance methods, constants, and refinements, why not? So it's supposed to work. But the problem is that once you do this, you open a can of worms. This is called the dynamic scoping. This is what we are used to with Ruby. We close scopes. We are open scopes. Some people noticed, for example, in particular people from the JRuby team, Charles Nutter, that if you do this, then you have a few problems that might get you. Now, this is the part of my presentation where I wish I had a couple hours because it's actually quite a lot of stuff. But I don't, so I will have to be fast. And if you wish, you can approach me later and ask for more details if you're interested. But all I can do now is give you an artfully complicated example here that I created just to show you what might happen. Here it is. I'm defining a method named add that is taking two things, probably two numbers, and using the plus method on them. And then I have two classes, some class and some other class, and I'm calling add in the context of these two. And then I'm using some kind of refinement that the documentation says is refining fixed num plus, and then I'm calling add again. Now, with dynamically scoped refinements, you might have a situation such as this one. Look at the three results in the three cases. In the first case, some class is not refining anything relevant here. So you get two just like you expect. Some other class is apparently doing some psychotronic casting. It was probably written by a JavaScript developer. So it converted numbers to strings and then it concatenated the strings, okay? Just because. And float point math is deciding that it will never return integers. It always returns floats. So it's returning a float from the operation. Think is, if you look at this code, how do you know what's gonna happen? I mean, these three operations look exactly the same. The only way to know what's gonna happen is to know exactly what is happening inside the classes. Whether any of these classes is using any refinement via any possible way in your entire system that changes the behavior, not of add, but of any method that is scald by yet. Now, some people say this is very confusing. Some people say this is terribly confusing. Some people say, oh, it's just a ruby. I mean, you can already make a mess, right? So there is no consensus on this actually. I don't know what to think. I don't have a strong opinion personally. It's very hard to predict what would happen in a real system. Some people say this is implementation leaking into your interface. Now, you cannot trust anything to be what it looks like. Anything can change in the space of a single line of code. Well, the other camp says, yes or what? The first camp, the side of people who worry about this also say, wait a minute, this also slows down Ruby. I won't go into the details again. There are technical details because of the way Ruby interpreters are implemented. That mean that for many interpreters, if not for all of them, these features slows down not only the code that uses refinements, but every piece of code in the system. Essentially, just like you do, you have to check whether there are refinements active in your code. The interpreter has to do the same. It cannot just trust things to be what they look on the surface. It has to go in and check whether there are refinements active before it can execute code. Once again, some people say, this is a disaster. Other people say, well, let's just optimize, huh? Another problem is that when you don't understand your code, you are also vulnerable from a security point of view because now you don't understand what is happening and I can trick you into executing code that looks harmless, but it's actually dangerous. Once again, no consensus. I was talking to André yesterday because he had the presentation on security and he was like, well, I can already trick you into executing code that is dangerous. I can monkey patch code and then monkey patch it back to what it was and you will execute it, guaranteed. And, well, yeah, maybe. One final problem with this implementation of refinements is that there are a few corner cases that might catch you off guard. Again, I won't go into all the details, but just to say one of them, if you execute this code in a common line interpreter such as Pry or IRB, this last line will not work, actually. Will not work like this. The refinement will not be active. If you execute it in a file, it will work. The reason for that is a very sound technical reason, but as sound as it is, it will still catch me off guard. It will still surprise me. So to cut it short, there was some drama. There was a lot of debate. If you want to wrap it up, there is one hugely good side about dynamically scope refinements. They fix the monkey patching problem. They fix the global monkey patching problem. On the other hand, they come with a few strings attached. They make your code potentially confusing. They probably slow down the language. They might have security issues and they have weird corner cases. So there was a debate. If you have one afternoon to go to the internet or read the debate on the Ruby Core mailing list, even a few weeks, I think, before Ruby 2 was released, there was still a raging debate about it. And the core team had to decide are dynamically scope the refinements worth the effort and the risks. And for now, they decided no, they are not. They're not. So the refinements you have seen, the dynamically scope the refinements are not the refinements you have now in your Ruby. Let's look at those. They look pretty much the same, okay? Same stuff you have the one-two process. You define the refinement. You use the refinement exactly like you did before. But this use case, where you reopen the scope, doesn't work. Neither does or you open in the scope by any other means. With the class keyword, with inheritance, it doesn't reactivate the refinement. The refinement stays there, okay? It stays there where I have that small mark from the point where you use using, right after the using, to the end of the module or the end of the file, or the end of the string if you are creating a string that contains the refinement and passing it to eval. But it stays there. This is called the lexical scope. It's lexical because it's about text, right? It's like, it's literally there in that piece of text. It doesn't leak out. No other code from the outside can see the refinement. So in this example, again my artfully complicated example. What happens now? Some class class eval, I don't mind whether some classes are refined or not. The only thing that can happen there is that I get a two. Because even if it is refined, I will not see the refinement from here, from a separate scope. And the same is true of some other class class eval. But this one, what happens here? Will I get two, or will I get two dot zero? I'm taking bets on this one. Matt, so you don't need to reply. So, okay, I tried it. Why is it two? It's two because this is purely lexical, okay? Remember what it means, it's about text. And if I say add here, and nobody's refining add, I will get whatever add retires. Somebody is refining plus, which is called by add. But the call to plus does not happen after the using. The call to plus happens there in the second line of the code, up there. And up there, no refinement is active, okay? It is kind of surprising. But once you know how it works, frankly, it's okay. But it can catch you off guard once. It already did, so it won't do it again. This means that this code has almost no, no confusing part to it, okay? Almost. But let's look at the three use cases. So you remember why we introduced the refinement in the first place, right? To replace monkey patching, and monkey patching had at least those three use cases. So let's look at the use cases again. The first one is domain-specific languages. What's the point here? The point here is that you are passing a block to a method named it. Let's add parenthesis, so it's clear that it's a method. And inside this block, you have a new method named should. So there should be a refinement in there that adds the should method to, I don't know, object, basic object, fixed num, the kernel module, something. But this cannot work now. It cannot work with lexically scoped refinements, right? So this does not work. So this use case is not covered out of the box. I can cover it if I had a using right before this code. And in fact, that's probably one of the reason why our spec moved to a different syntax that doesn't require refinements and minimizes monkey patching. Convenience methods, will these work? Not out of the box, no, not straight away. It cannot work because once again, I'm reopening a scope and expecting to find some refinement stuff in there and it's not there. It cannot be there. If I reopen a scope, it's not gonna be there. I can fix this, this would fail and I can fix this by adding a using. But I have to add the using. To every model class, for example. And now it's working. It's not very dry. The third use case, method the wrappers. This one is good. Refinements have another very nice feature. If I call super from inside the refinement, I will call into the original refined version of the method. So refinements are actually great for this specific use case. This works, it stays local. It doesn't leak out. It replaces monkey patching just fine. Still, if you look at the entire thing, if you take a step back and look at big picture, refinements today, lexically scope the refinements, do not fix monkey patches. Not in general, not in the general case. And they still have a few surprising corner cases. On the positive side, they do fix monkey patches in some cases, or at some cost, like the cost of repeating your using into every file where you want to use the refined methods. They do not make the code particularly confusing. They do not impact performance. They do not impact security. And this is crucial, I think. They pave the road for something more in the future. Maybe more powerful refinements. Maybe something similar. Maybe something we are not expecting. This got me thinking about the problem. So, look at this code. Once again, it's what we in the Ruby community consider beautiful. It reads smooth. It's expressive. Whether you like our spec or not, the syntax of this is quite neat. But if you look at this code, there is a white can exist. This is because of a number of features in Ruby that frankly do not make that much sense when taking on their own. Right, the fact that, like the fact that I can skip parenthesis. I mean, we're used to it, but we're never, I don't know, when people say, why does Ruby have optional parenthesis in method calls? The usual answer is, well, Mets liked Perl. Singleton class is a metaprogramming. Why do they exist? Well, Mets likes small talk. And you have to wonder, was Mets thinking about this? I don't wanna know, okay, Mets? But was Mets thinking about this when he decided for Singleton classes? I would bet he was not. I mean, Singleton classes in the first versions of Ruby look like an implementation detail, right? And then people started to abuse them and we came up with this, and it's pretty awesome. It's actually, there is some serendipity to this that is pretty awesome. So this is the thing, that language design, you cannot just sit down and design a language. That's not how it works. Oh, you can probably do it. You end up with cobalt. But if you want to build a beautiful language, that's a deep problem, right? It's about trying out things and see how they turn out. I believe, I'm starting to believe. So in particular, dear Mets, I have to say, when I looked at refinements, dynamically scoped the refinements, I was pretty scared. I'm still kinda scared somehow. And, well, we met at the conference a few years ago and I told you in private that this language that you created, it changed my life. And you answered in a very, you answered, oh, it's a big responsibility. That was a very Mets' thing to say. And it's a big responsibility indeed. I think it applies to all of these rooms. I don't have a lot of time anymore. And one thing that I was considering, I was scared and the reason why I was scared is that I'm a bit of a wimp. That's why I do not design languages. I'm so glad that you aren't. You and the core team. I'm so glad that you are still willing to make experiments, right? To see what happens, to put a new feature in the language and see what happens after a few months once people start to get the hang of it. And, well, that's what made the language, I guess. I guess that I'm trying to say thank you. So, thank you. And thank you all for buying this book. I hear it's good.