00:00:48.559
Cool, but why is our next talk by John of Italian origin? If I recall correctly, hi John! Thank you, lovely. It has been a great day—tiring, but generally a grand day. I wasn't expecting it to go so smoothly after the first few hiccups in the morning.
00:01:07.840
But good thing that everything is working much better right now. Yeah, how are you?
00:01:22.400
I'm good, yeah! I've tried to make the lighting in here reasonable. This is my first time doing a remote conference, but I'm excited.
00:01:27.920
Same here! That's part of the excitement and stress—what can go wrong today? Thank you so much for joining us.
00:01:42.240
For our attendees, this is going to be a live talk.
00:02:06.079
Thank you! I assume you can all see my screen at this point.
00:02:17.680
So, if you're like a lot of people when you're first learning Ruby, you're going to go through a few stages. Let's see if these sound familiar. You start out skeptical. Maybe you've heard that Ruby is slow, and all of your friends say that it can't be used for writing real software. They'll send you links to articles about sites built in Ruby that had to move off because of scaling problems.
00:02:41.519
You'll almost be convinced until you realize that every article you're reading was literally written in 2008.
00:02:54.560
Next, you might be confused, having never seen a language that looks like this before. You're wondering, where did all the punctuation go? Wait, array iteration is just another method call? Oh God! Then you're thinking: wait a second, am I having fun programming? Maybe! As the language wins you over and the code starts to flow, you're naturally going to get excited.
00:03:22.959
Everything's an object. The standard library has so many great things, and there are sensible names everywhere! Why don't all languages just use sensible names? And the community—you all are amazing! Now you're writing real code, things are coming together, and you're loving it.
00:03:40.799
This is about the point where some cranky senior programmer walks in the room, looks at your work, and says something like, 'This code is not very Ruby.' What does that even mean?
00:04:00.239
You may even find yourself in this position later, saying the same old stuff to other newer Ruby developers. We just don't use that feature; they were writing Python. Or, my personal favorite, and probably the one you hear most often: 'It's just not very idiomatic, is it?'
00:04:36.720
While often, the advice you receive originated in a good place and with good intentions, more often than not, the person giving it doesn't have the backing of why. They're just repeating something that they heard. They can often be wrong, misguided, or have assumptions based on evidence that just isn't the case in modern Ruby anymore.
00:05:18.880
I'm here to say: don't be a parrot!
00:05:24.160
Unless you are an actual literal parrot, in which case, please just continue to be a parrot. Okay, so everyone repeat after me at home: don't be a parrot!
00:05:36.720
If you just repeated it, then you're not getting it yet. Today, I'm going to dig into some common things that are seen as 'bad Ruby.' I'm going to talk about why and I'm going to go through some code examples to demonstrate. I don't need you to say, 'We don't use that anymore.' I just need you to say, 'We don't use that because...'
00:05:55.199
The things that I want you to take away here are a set of practical tools as a starting point and a mindset that you can use to become a better developer and a better mentor.
00:06:16.480
Before we get too deep into this, I should introduce myself. My name is John Crepezzi, and my internet handle pretty much everywhere is @cjohnrun. I live in New Jersey in the United States with my family.
00:06:39.120
No Italiano—Italian imperfecto! I love Italy, and I wish more than nearly anything that this pandemic was over so that I could be over there with you in person. A few years ago, my wife and I were at a bar in Milan, and we overheard an American couple. It was a cocktail bar, and as our cocktails came out, we heard this couple at the next table say, 'No one drinks Campari in Italy anymore.' Well, as soon as I'm able to, I will be back to disprove that statement.
00:07:19.200
I'm a staff software engineer at GitHub. I've been at GitHub for a bit over three years and I've worked on a ton of really fun features there with some amazing people, including Eileen and Aaron. I've been working in Ruby professionally since 2009. I've worked on a bunch of open source software, and I've worked at Patch, Genius, and Square. I've also started a few startups along the way.
00:07:45.600
This is all just to say that I've definitely put in my 10,000 hours at this point, but I don't consider myself an expert. That's primarily because I think that experts—there's so much to know about software, even a relatively minor topic like Ruby, and it's constantly evolving. So, the only thing that you should trust is known, and even then, only temporarily is your own experience.
00:08:05.440
So, let's get back to that. And maybe at this point, you're thinking, 'So, if we shouldn't trust what people tell us, and we don't want to be parrots, then maybe we shouldn't trust you, and we shouldn't listen to you.' And it's like, first off, rude! You know what? No, you shouldn't trust me. Just for that, I'm going to sneak one lie into this presentation, and you get to try to spot it.
00:08:31.040
So, just a fair warning up front here: I'm about to show and talk about a ton of code. It's going to be fast, but I'll share a link to all of my examples at the end of this presentation. I encourage you to check them out locally, experiment with them, and then come to your own conclusions.
00:08:57.360
Let's get started, and I'm going to start with for loops. This is one of the most common topics for developers coming from other languages who are new to Ruby.
00:09:08.480
For loops provide a simple way to iterate over any enumerable. You can see in the bottom example that you can even do a handy destructuring with for, similar to how you'd use each on a two-dimensional array. But we're told not to use them. We're told they're not Ruby. Cool, but why?
00:09:35.040
Let's start with the reason that you maybe hear from people: that's because when you're using each, you're creating a new scope. You can see here inside of each that I'm making a new variable called 'new_var,' and then outside of the block, if I try to access it, I no longer have access to that variable, which produces a name error.
00:09:52.000
Contrast that with using for. When we define new_var inside of a for loop, it isn't created inside of a new scope. These variables stick around and continue to be accessible outside of the for loop. And if you're like me right now, you might be thinking: who cares? That seems like a pretty weak reason not to use something.
00:10:09.760
After all, if those other keywords that we use pretty often, like while, until, and case, work the same way... You might be surprised to see that, due to the way that the Ruby parser works, you don't even need to use these variables for them to be defined. But that's probably a topic for another day.
00:10:28.160
Yes, despite these other keywords working the way they do, we don't hear many people proposing something crazy like implementing if using blocks—although looking at it now, I kind of don't hate it.
00:10:50.000
Let's learn a new tool real quick. This is Benchmark! It's part of the Ruby standard library, and it gives us a quick way to compare multiple implementations of something. We require the benchmark and then write multiple versions to see how they perform. In return, we're going to get out this list of the versions compared to each other on several different dimensions.
00:11:26.160
For our purposes today, the one that's really easy to conceptualize is the last one: it's the real category. This one measures what you kind of expect—it's wall time—how much time that version actually took to run on a real clock.
00:11:54.959
If we run a simple benchmark on that funny if block version that I showed previously, we're going to see that the runtime of this block form is about two times slower than the keyword version. So maybe that's a reason not to use these block versions. And then maybe you see where I'm headed.
00:12:11.680
Next, if is faster than the if block predicate version. It would stand to reason that for should probably be faster than each. Here, I've written six separate implementations of a basic for loop. Let's see how they do against each other first.
00:12:45.200
And not too surprisingly, wall and until are nearly identical. Also not surprising based on the performance of times and up to—are also very similar. Even each is very similar to times and up to, and that makes sense; they're all methods that make a call and then yield to a block.
00:13:00.080
But what about for? Well, here we can see that each in this benchmark is actually faster than for, and that's pretty weird, right? Actually, if we include the iteration inside of the loop but run the loops multiple times in the benchmark, we'll see the numbers diverge even more.
00:13:23.680
How can this be? This is the point where we should all be happy that Ruby is open source; we don't have to wonder because we can go look at the source code.
00:13:38.960
So we go deep—6,500 lines down into compile.c—and this is the C code that handles implementing for. I don't need you to know C, but if we look right here, we can see that Ruby's implementation of for literally sends to each.
00:14:17.760
For is implemented in terms of each, just like our if predicate method was implemented in terms of the keyword if. So now the evidence is stacking up quite a bit. You shouldn't avoid for because its syntax is confusing or because it's ugly, or because it's not Ruby.
00:14:31.920
But you might choose to avoid it because it's an unhelpful abstraction on top of a more flexible method called each. I realize the technical content here is pretty dense, so I'm going to mix in some photos of my favorite European Ruby conferences from last year.
00:14:56.640
This one was in February of last year. This was Ruby on Ice in Tegernsee, Germany. We had the opportunity to stay at what I think is probably the nicest hotel I've ever been in. It was directly on the lake, and afterward, I took a week off, which is something I pretty much never do.
00:15:12.880
My wife and I went over to Salzburg for a week. This was a really nice trip, and going outside was awesome.
00:15:36.480
Now, let's look at something completely different: method_missing. This is a feature that people love to hate, often embraced by people who like to feel clever. Method_missing is both a blessing and a curse, and it's also typically seen as a code smell.
00:15:49.600
But why? Here's the default implementation of method_missing that exists on BasicObject. Again, don't worry too much about the specifics, but the Ruby equivalent is essentially raises NameError. Because the Ruby gods have exposed this method on BasicObject, that means subclasses can override it and supply custom behavior for what happens when a method isn't defined.
00:16:10.239
Here's a simple illustration. In this example, we've created a custom implementation of method_missing that lets you call any method and then returns the name of the method you've called backward as a string.
00:16:34.000
To make a slightly more useful example, here's a quick and dirty way that we could abuse method_missing to take a hash and make its members accessible via method calls. This is a very dumbed-down version of something like the Hashie library that you may have used in the past.
00:16:52.639
The thing everyone knows for sure, and what probably has a few of you squirming in your seats right now, is that if you define method_missing, you also need to define respond_to. But why? The reason you need to define respond_to is that if you don't, anything trying to determine if a certain method exists is going to receive the wrong answer.
00:17:12.960
Does something that's defined by hash respond to 'a'? No, it doesn't. But if you define a customer respond_to, well, now it will. So at this point, everything is perfect; respond_to returns the right thing, we can call the method.
00:17:39.840
We can even look up the—oh damn it! The thing is that since the method was never explicitly defined, Ruby doesn't know how to find it. Luckily, Ruby provides a way to fix this. Instead of defining respond_to, we can define respond_to_missing, and now our method appears correctly.
00:18:02.480
Of course, things like public methods still won't include these method methods, but that's understandable. Is something maybe tingling in your brain, though?
00:18:18.720
Why did we need to define a custom respond_to_missing? Why wasn't defining respond_to enough to get method to behave correctly? And more importantly, what does the fact that it wasn't enough to define just respond_to imply about the behaviors of these methods?
00:18:40.000
Let's bring our benchmarking tool back out here. We're comparing calling respond_to on a real defined method versus calling respond_to asking about a method that's only defined through method_missing.
00:19:07.840
No surprise—the real method call is slightly faster! Not a very convincing reason to not use method_missing, though. It's when actually calling the method that the true difference is going to start showing itself.
00:19:32.800
Now we're seeing more of a difference in call times, and it should be pretty clear why. The first example can find the method and call it directly, while the second example goes through a level of indirection to get to the right place.
00:19:59.040
So, let's just draw that out to really illustrate the point. On the left side, we have a regular method call—we call the method, we look it up, we find it, and we call it.
00:20:08.720
On the right side, when we call a method, we have to look it up, and now it's missing. Now, instead of being able to call method_missing, we have to look up method_missing, and then when we find that, we can call it.
00:20:24.320
It's this extra step that makes method_missing definitions a little bit slower. Okay, so we've settled on maybe one reason to choose not to use method_missing unless you really need to.
00:20:49.840
And that's because it's, pound for pound, slower than a defined method to actually call.
00:21:00.480
But that's probably not enough of a reason. There's another problem, though. Let's take a look at what happens if there's a failure somewhere inside of a method_missing call.
00:21:19.440
You can see down here in the bottom error backtrace that we've called this method apple—it's errored—but the backtrace contains no mention of the method that we've called. That's going to be pretty confusing to try to debug.
00:21:33.840
Now, can you fix it? Sure! But we're getting closer to the primary reason that using method_missing is typically avoided, and that's because the method_missing implementation is super complicated.
00:21:55.040
Bordering on impossible to implement correctly in a way that doesn't hinder developer ergonomics or cause significant performance overhead. Rails, for example, goes to intense lengths to give developers a good version of method_missing that feels natural.
00:22:11.040
Wondering how they do that? Here it is! You can see that when you call dynamic_matcher in Rails, it literally defines the method and then immediately calls it. This keeps the backtrace clean and avoids the performance penalty on subsequent calls that would typically come with method_missing.
00:22:29.200
So is this a good option for developers? How do we define methods in Ruby? Are there any drawbacks here?
00:22:49.680
Well, you probably saw where this was headed. We're going to dig into define_method and we'll figure out what the deal is in there. But first, it'd be crazy not to talk about last year's Eruko conference! This was June of 2019, and I mean, come on, it was on a giant ship!
00:23:17.360
The right side here was a brewery nearby that we spent a lot of time at, and it was called Caps. Just like going outside, it was really nice.
00:23:30.160
Okay, back to define_method. Imagine you want to create a class with two methods that are identical except for their name. Define_method is a way to define their behavior quickly and with less repetition.
00:23:43.360
You can see that I'm iterating through an array of two symbols, foo and bar, and for each, I'm defining a method that just returns the same symbol. It's tempting, but you're often going to get pushback trying to put code like this into production.
00:24:12.960
Cool, but why? For all meaningful intents, these methods are just like all the other methods that you might define. Calling respond_to on an instance of something returns true; calling the method works fine.
00:24:37.760
Looking up the method works fine, even trying to list the methods will contain the correct symbol. They behave just like methods! So we should be able to compare these two side by side, and we expect them to perform the same, but they don't!
00:25:00.960
Okay, so defined methods are slower to call. Why? Clearly this isn't just the method. If we drop a pry debugger into the middle of all of this, we can get a hint of what's going on.
00:25:25.920
We look up the definition of foo, and we get back the defined_method block instead of receiving back the actual method definition. This kind of hints to us that maybe the thing we're defining isn't a method after all.
00:25:49.760
Resorting to some old tricks here, let's go to the relevant part of the Ruby source code for define_method. Okay, so define_method is actually pretty complicated because it can take in more than just blocks.
00:26:11.200
So how about we just zoom in here on the relevant piece? In this part of the definition, we can see how define_method actually works. It creates a proc copy of the body that was passed in, turns it into a lambda, and then it defines the Ruby method that you're defining in terms of the proc that it just created.
00:26:28.240
Here's a roughly equivalent piece of Ruby code that should help illustrate what's going on and why methods created via define_method are slower than normal methods to call.
00:26:51.600
They're slower because instead of calling the method directly, you'll always be calling what essentially amounts to an instance of a valid copy of the original block.
00:27:12.400
But it doesn't stop there. There's another issue with our typical uses of define_method that's problematic.
00:27:25.920
So I'm going to break my format a little bit here to look at another class of problem that might not be super obvious, but one that you should be keeping in mind when using define_method. And in fact, as we'll see, all other methods that take a block.
00:27:44.640
When you're using something like define_method when setting up a class inside of the block, you'll have access to any objects that are currently in scope. You can use them inside of your block.
00:28:09.680
Here, I'm using a local variable called greeting inside of a define_method called greet. Unfortunately, that comes at a cost. You see, Ruby uses reference counting to keep track of garbage collection. Normally that works really well, and you don't have to think about it too much.
00:28:29.760
But when you introduce a block like the one for define_method when creating a class, since Ruby can't tell what things you might reference inside the block, it needs to keep all of them accessible.
00:28:41.680
That means that it can't free any of the objects that could be reached from inside of that block until the block is freed. In this example here, of course we can all agree that greeting doesn't get garbage collected, but it might be surprising to hear that neither will other_var, despite the fact that it's literally never used.
00:29:05.760
Just saying that is all well and good, but you probably expect me to prove it, and that's exactly what I'm going to do.
00:29:24.880
Ruby comes with a useful library called ObjectSpace. Just like Benchmark, we have to require it to get access to these methods, which, by the way, should only ever be used in development.
00:29:42.720
I encourage you all to take a full tour of this module yourself, but just to get you started, I’m going to shout out some of my favorites. So, count_objects_size will give you essentially a hash back that has all of the types for Ruby and how many bytes they're currently taking.
00:30:03.840
memsize_of_all will take a class in and give you back the consuming memory total of all the living objects of that class type.
00:30:23.760
And each_object will actually let you put in a class and you can get, you can pass a block that will receive each of the current alive objects of a given class.
00:30:38.480
So, given those tools, it should be pretty straightforward to put together an experiment here. This is a quick and dirty method that just runs garbage collection, counts the total memory size of a certain class, yields to some code, and then reports back the difference after the block is over, after running garbage collection.
00:30:55.440
So, essentially by wrapping code in this block, we're able to see how many bytes of memory were created for a given class inside of the block but then not able to be released.
00:31:11.520
If we run this, and we create a string inside but we don't reference it anywhere, we can see what we expect: zero bytes. That means that the string 'str' was able to be garbage collected before our block was over.
00:31:27.680
Now we make a quick modification. So what we've done is we've defined the string to be scoped outside of the block, and we can see what we expect here: again, the string doesn't get garbage collected, and we get back 40 bytes of unreleased string.
00:31:45.840
It looks like everything is working with our experiment, so let's jump back to where we were before. We're going to wrap our greeter class definition in our test method and you're going to see exactly what I was saying before.
00:32:04.080
Ouch! 10,081 bytes of a string that won't be garbage collected despite the fact that it's not being used.
00:32:22.160
Now for a more meaningful illustration. Imagine that we're loading a bunch of data in order to populate a list. We open a file, we pull some data out of it, and we put it into a constant called band_users.
00:32:42.960
Later on in the class, we use define_method. Now it's very obvious that band_users is not going to be garbage collected. But here, by mistake, we've actually taken away Ruby's ability to garbage collect.
00:33:02.960
In this case, we even close the file pointer for the file that references or was referenced by banduser file.
00:33:20.480
As a result, we've retained 8,424 bytes of unreleased file. And just to be really clear, this isn't just a problem at the class level; it can happen anywhere that a block is passed to a method that holds on to a reference to the block.
00:33:41.040
In this example, we're calling parse and we're calling add_callback, and the file handler defined by file will not be released until the actual user object itself is garbage collected.
00:34:01.680
If you nail out these references, you're going to let Ruby's garbage collector continue to do its amazing job. And this is how you have to fix this issue if it's something that you're encountering in your application.
00:34:19.760
So, define_method isn't free, and that doesn't mean don't use it, but it does mean that you're going to pay a constant cost on every call to a method that was created via define_method.
00:34:35.760
And that you have to be careful of what objects you're unintentionally retaining. But is there a way to avoid that?
00:34:50.800
There is, but you might not like it! Class_eval has the benefit of creating real methods, and due to the way that they're evaluated, they can't hold on to any closure context from the surrounding scope.
00:35:07.440
So, in this case, other_var would be garbage collected as soon as foo is finished being declared, and garbage collection runs.
00:35:28.000
So just to be really exhaustive here, here's a benchmark showing that the methods created via class_eval are 100% identical to real Ruby methods. They have a higher upfront cost to be created, but they incur no extra cost per method call.
00:35:46.400
If you've ever seen a bunch of dynamic code, class_eval for a hot code path—this is why! In fact, Rails's define_method that I showed you earlier uses class_eval to define dynamic matchers.
00:36:03.440
It's tempting to overuse Ruby's meta-programming features. It can result in less code and, more importantly, less duplication.
00:36:18.080
But you really need to know what you're doing, and you need to be aware of the potential trade-offs that you're making.
00:36:34.160
On the flip side, every path doesn't need to be fully optimized. Things like class_eval definitely make code harder to reason about— they make it harder to search, and ultimately harder to maintain.
00:36:49.280
You need to make the choice that's appropriate for your use case.
00:37:05.440
So, okay, you caught me. These are not from a Ruby conference! This is just our summer vacation last year. This is at a VS day down in Puglia, and the water is just so clear.
00:37:21.440
These are my daughters, and this daughter's name is Ruby.
00:37:33.040
Okay, let's go for a lightning round! I'm not going to go all the way on these; I'm just going to start you off with some things to think about.
00:37:48.960
Use symbols over strings whenever reasonable. Cool, but why? Historically, the reasoning is that strings are mutable, so they're new objects every time that you create them.
00:38:04.160
You can see here that if I create two strings and I compare the object IDs, they'll be different. Symbols are immutable, and due to that, they always point at the same objects, thus they cause less memory churn.
00:38:19.440
You can see here I'm creating two symbols with the same value, and the object IDs are the same. That's the reason you're going to hear from people. But a lot of these once compelling reasons are less so now that frozen strings exist.
00:38:36.720
By the way, do your own benchmark, but frozen strings are just as fast as symbols when it comes to lookup. Still, many developers choose to use symbols as a signaling device of immutability, and standardizing on them, especially in control flow circumstances, avoids unnecessary markup and reduces confusion about what type of object you're handling.
00:38:54.080
Did you ever get an object and say, 'These are not equal, but I expect them to be,' and it turns out you were holding onto a string when actually you were comparing to a symbol?
00:39:11.840
Also, somewhat obscure markup tip: on the bottom here, you can create symbols with interpolated values like this: interpolation is not a reason to have to use strings.
00:39:32.680
The next one is always use attr! Cool, but why? You'll often hear to use things like attr_reader, attr_writer, and attr_accessor. I say use whatever version feels most natural for your use case.
00:39:53.680
But just to really set your brain off a little bit and get your mind spinning, I will mention that the attr definitions are slightly faster. So why don't you go find out why?
00:40:11.920
You'll also hear: don't use keywords like 'and' and 'or.' Oh wait, that's confusing to say! Don't use keywords 'and' and 'or'—okay, never mind. Cool, but why?
00:40:29.280
Okay, so a regular double ampersand behaves like you're used to in all programming languages. The entire right-hand side of the statement here is evaluated before assigning to foo, and foo becomes three.
00:40:53.440
The keyword 'and' is a lower precedence version of double ampersand, so the behavior is pretty wildly different. For illustration, you can think of the keyword version here essentially as first evaluating foo equals two and then ending with three.
00:41:07.760
Even that isn't quite right, because the 'and' version returns two, while the double ampersand version returns three. This is why people avoid using it. It's said the same way out loud, yet it behaves very differently and can introduce very subtle bugs.
00:41:23.440
This one I pretty much agree with—just don't use them.
00:41:33.920
Another one you're going to hear pretty often is: don't write comments. Cool, but why? You know what, actually, I take that back— not cool! Write comments, please, and if you want to, tell people they tell you not to. Find something else to worry about.
00:41:48.400
Phew! Okay, we are nearing the end here. And if I can have you only take away one thing from this talk, it's that hashes should not have spaces inside of them! I mean, really just think about it.
00:42:12.960
But if I can have you take two things away from this talk, then the second one would be that questioning accepted wisdom is the fastest path to growing as a developer.
00:42:34.720
Reading the headline of an article shouldn't be enough to convince you of something. In fact, even reading the full article shouldn't be sufficient.
00:42:50.720
We're engineers, and the way that we learn is through experimentation. Through this, you're going to grow as a developer, and when you do, you're going to help the other people around you do the same.
00:43:07.200
I hope that in this talk, I've given you some tools to help on that journey and pointed you in some new directions to explore.
00:43:15.240
Thank you for having me, and enjoy the rest of the conference.
00:43:30.800
Hello, you're going to help! I was waiting for the... and your line that didn't come!
00:43:39.360
So, this has been great! They're only super fun. If you look at the chat afterward, everybody has had a ton of fun.
00:43:47.680
Thank you so much! Great to be here, making us experience again a little bit of that joy in finding why.
00:44:02.760
I don't see many questions, besides one that is like: which was the lie? But that's not something you should answer, to my understanding.
00:44:12.160
Although I do want to introduce the possibility that the lie was that I was going to tell a lie, so just, you know, let your mind wander!
00:44:25.440
Let's see if someone else has questions. Amazing performance! I'm reading so good job.
00:44:54.320
We have a bit of a delay between the streaming and what is shown. Uh, there it is: the question I was waiting for. Should we prescribe this dog when writing a Rubocop config?
00:45:07.040
I think it's a bit of drawing.
00:45:14.760
But yeah, I know that that makes a lot of sense. I think the two things that stuck with me were the ones about the symbols and the and or thing.
00:45:47.920
When I was in my previous company, we had, I swear to God, it was like a meeting with 20 developers in a room with our team lead, and the topic of the day was: should we go for single quotes or double quotes?
00:46:03.680
And mind-blowing!
00:46:12.960
But yeah, so that was also a remembrance of that conversation, because there was no real reason for really sticking with either one or the other all the time.
00:46:21.760
That brought me to look a little deeper into this and or or conundrum.
00:46:37.040
Early in my Ruby experience, I was very into single quotes as a way to indicate that there was no interpolation in the string.
00:46:51.760
But do you really need that hint as a way to do things? You can pretty much see!
00:47:05.760
And then I remember back then someone publishing, almost jokingly, a benchmark about single quotes versus double quotes.
00:47:16.960
Just trying to point out how ridiculous the argument was.
00:47:24.720
At GitHub, we use double quotes for everything just as a matter of consistency, and it honestly kind of annoys me a little bit. But it's not worth a fight.
00:47:35.120
Yeah, definitely! That is the other thing: if we are talking about performance at this level, again, probably the most costly part of the whole application is us.
00:47:45.600
So just short-circuit everything else and go for one, because it's not worth the fight!
00:47:59.360
Definitely! There is a question from Aaron: if you use define_method with a lambda, does that make it a blocking method?
00:48:10.720
Yeah, it was a very valuable meeting, Antonio—to leave back and forth. People are asking this. 'Cool, but why?' is already a meme! We can create one.
00:48:30.720
I'd like that! That sounds great.
00:48:40.320
Half the keystrokes, twice the speed!
00:48:51.680
Good point! You see, we can keep debating about this for the rest of the day.
00:48:58.880
I think anyways, John, it has been great to have you here. I just have to laugh so much and to learn, actually, so much after 10 years of Ruby development.