Talks

Dropping down to The Metal™

@chancancode
As much as we love Ruby, when you need to be really close to the metal, you have no choice but to use JavaScript. This is why I developed the javascript gem to help you harness the raw power of your machines. In this talk, we will examine the Ruby tricks and black magic hidden behind this ludicrous invention. Along the way, we will learn about how Ruby internally deal with variable lookups, method calls, scoping and bindings. Together, we will push the limits of the Ruby language, taking it to places Matz never ever envisioned!

Talk given at GORUCO 2015: http://goruco.com

GoRuCo 2015

00:00:14.759 Uh, I'm Godfrey. You can find me on the internet as Chanen Code, and I work for a consulting agency in Vancouver called Brew House. We basically help people design and build stuff. So if you need help building your next Rails app or JavaScript app, please give us a call. We're an awesome team.
00:00:22.640 As Luke mentioned, I am from Canada, and according to what I hear, we are essentially known to Americans for a few things: our Canadian accent, the healthcare system, and Texas? Uh, I'm so sorry for the typo; wrong Texas. If you want to learn about or practice your Canadian accent, I've made a gem for you. Simply install 'Canada' on your computer, and you'll be able to sound just like a true Canadian.
00:01:12.640 I am also a member of the Rails Core team. As you learned from Allen this morning, this means that I get to tweet from the Rails Twitter account. However, beyond that, this connection does not add any credibility to what I'm about to tell you, and it's very important for you to remember that for the rest of the talk.
00:01:24.720 What being on the Rails Core team does mean is that I get to work alongside some really smart people like DHH, Aaron Patterson, and Eileen. On one hand, it's awesome to learn from these leaders, but on the other hand, it sometimes makes me feel inadequate. They are thought leaders and Ruby heroes, and it begs the question: what am I doing here? So I decided it was time for me to up my game and become a thought leader myself.
00:01:47.800 Fortunately, there's a lot of resources out there these days for anyone willing to learn. You can probably find this book in your local library that promises to transform you from a regular dummy to a thought leader. However, that's a little too basic for me. My personal favorite is another book from a different publisher, written by some of my favorite thought leaders in the industry.
00:02:04.440 I ordered a copy of this book on my Kindle, and I was really excited to find out what was in store for me. I tried it out, and this book definitely did not disappoint! I only had time to read the first chapter on my flight here, but I learned some very valuable lessons that I can apply to my talk today. I learned that one common tactic among thought leaders is to create controversies. For example, David famously pronounced the death of TDD at RailsConf last year, which sparked a lot of debates and made him really famous, even among people who do not know what Rails is.
00:02:28.480 So, inspired by this, I decided to make this talk as controversial as possible. To start, I would like to announce that thought leadership is now dead. My name is Godfrey, and I'm allowed to think whatever I want. Take that, thought leaders! Seriously though, all the Rails contributors are incredibly talented individuals. If you're interested in contributing to Rails or just keeping up with the latest changes, I help run a newsletter called "This Week in Rails." You can subscribe on the website or read it on the Rails blog every Friday.
00:03:04.640 You should also come and talk to us at the conference; we would love to give you pointers to help you get started. Anyway, that's enough about Canada and Rails. Let's get to the real talk! Alright, let’s talk about JavaScript. I know we all love Ruby—that's why we're here—but deep down, we all know that unlike JavaScript, Ruby is not web scale. Every once in a while, you will need to get really close to the metal, and you have no choice but to use JavaScript.
00:03:38.960 Let’s talk about what makes JavaScript so fast. The secret lies in the syntax. JavaScript has a very fast syntax. For example, most runtime environments have Just-In-Time (JIT) compilers to make things really fast, but the JavaScript syntax allows you to drop in some hints for the compiler to make things even faster. Consider the case of a triangle: you know the lengths of sides A and B, and you're wondering what the length of side C is. You might recall from high school geometry that you can get this length by calculating the square root of A squared plus B squared.
00:04:06.640 Let’s implement the triangle formula in JavaScript. First, you would write a function to compute the square of a number, and then you'd write another function that actually implements the formula using the square function. Calculating the square root of A squared plus B squared is pretty straightforward, but what you might not realize is that you can enhance the performance of your code with additional hints, because JavaScript is a weakly typed language. Although it's obvious to you that the inputs are meant to be numbers, it's not clear to the compiler.
00:04:54.760 By adding some unary plus operators here and there, your code will automatically become faster because the compiler will now understand what you expect from those inputs. Voilà! Now your code is 5% faster—not too bad for just a few characters!
00:05:08.160 In case you don’t believe me, I made a chart. As you can see, it’s actually much faster. But as with most marketing charts, it didn’t start from zero, which is fine by me personally. However, if you insist—just reposition the x-axis and you’re all set. This is a quick example that showcases why JavaScript is such a powerful language.
00:05:29.680 That got me thinking: what if we could combine the fast JavaScript syntax with the Ruby virtual machine, which is full of magic? Perhaps we could achieve the best of both worlds and everything would be awesome! If you're sold on this idea, the code is already published on the internet. You can get it by running 'gem install javascript.' If you're impatient, feel free to download it now and ignore me for the rest of the talk. For those who wish to stay, we'll build this thing together.
00:05:40.840 Now, let's talk about Java for a moment. Let’s say, hypothetically, you have a Hello World program written in Java. It’s okay if you don’t know Java; this is basically the standard Hello World example copied from the Java website. Now, what do you think would happen if we save this file with an RB extension and run it with Ruby? Would it just work and print Hello World, or would it throw a syntax error or a runtime error? Let’s poll the audience.
00:06:06.960 Who thinks it’s going to be A? Alright, some hints. Who thinks it’s going to be B? Okay, more hints, and finally C? Okay, a few hints. As it turns out, the correct answer is B: a syntax error. What does that even mean? To understand that, we need to delve into how Ruby operates.
00:06:34.780 From your perspective, you just write some Ruby code and hand it over to the interpreter, and it runs the code for you. However, under the hood, there’s a two-step process. The first step is for the interpreter to try to understand your code at a syntactical level—a process known as parsing. Once it understands your code, it can evaluate it for you. If at the parsing step, the interpreter deems your code so broken that it doesn’t even understand it, it will refuse to continue and throw a syntax error, which is what we encountered earlier.
00:07:10.000 As your code is not even runnable, a syntax error is not something you can rescue and recover from. Conversely, if the interpreter understands the basic meaning and structure of your code but finds problems later during execution, it will throw a runtime error—this could be anything from a divide-by-zero error to a no method error. That’s likely something you’re more accustomed to dealing with on a regular basis as a Ruby programmer.
00:07:24.280 Fortunately, because our code is now running in the Ruby virtual machine (VM), we can anticipate these errors and rescue from them, allowing recovery. Enough about Ruby—let's return to JavaScript. Hypothetically, imagine you have a Hello World example written in JavaScript. What do you think would happen if you save that as javascript.rb and try to run it with Ruby? Would it just work, would it give you a syntax error, or would it throw a runtime error? Let’s see a show of hands.
00:08:01.680 Who thinks it’s A? Okay, no one. B? C? Wow, most of you are really smart! The correct answer is C: a runtime error. This is good news because now, having reached the second stage, the Ruby interpreter will actually understand the code at a syntactical level, meaning we can likely recover from this and make it work.
00:08:25.720 To understand how Ruby interprets this piece of code, we note that it thinks you're trying to find a local variable or method named 'console' and take the result of that variable lookup or method call to invoke the 'log' method. Remember, parentheses are optional in Ruby. We're passing a single argument to the 'log' method, which in this case is the string literal 'Hello, World.' The semicolon at the end can also be ignored, as it is optional in Ruby.
00:09:05.080 Looking at the error message again, Ruby complains that it doesn't know what 'console' refers to. Fixing this is straightforward; we can define a class with an instance method called 'log,' then assign an instance of this class to a local variable named 'console.' If we save that and run it again, it will function as expected, printing 'Hello World' on the screen.
00:09:48.760 However, there's a caveat: we are leaking top-level variables, which isn’t very neat. Additionally, we're mixing Ruby code with JavaScript code, which makes it a bit messy to read. Ideally, we want to keep all the Ruby logic in a file that we can require and contain all the JavaScript code within a block. In other words, we're trying to write a domain-specific language (DSL) in Ruby.
00:10:04.680 Fortunately, this is a well-established frontier in the Ruby community. If you’re familiar with Rake, ARPacker, or even R.rb in your Rails app, these are all examples of Ruby DSLs. The primary tool for writing Ruby DSLs is 'instance_eval,' which allows you to run a block in the context of an arbitrary object. Effectively, this lets you change what the 'self' pointer refers to when executing the block.
00:10:28.680 This is incredibly useful, so we can define a class named 'Context'—you can call it whatever you like, but I call it 'Context.' Whenever we want to execute some JavaScript, we instantiate this object and call 'instance_eval' with the block because Ruby allows you to omit the 'self' keyword when calling a method on the same object. This essentially lets you provide arbitrary global methods to the JavaScript block without actually using global methods, which is quite neat.
00:10:58.720 Now, let’s discuss variable definition. In JavaScript, you can define a variable like this: 'var message = "Hello World".' What do you think would happen when we try to run this code? Would it just work, or would it give you a syntax error, a no method error, or a name error? Before you vote, keep in mind that no method error and name errors are both types of runtime errors.
00:11:19.040 With that in mind, who votes for A? No one. Any hands for B? Any hands for C? And any hands for D? According to the Ruby interpreter, the correct answer is C: a no method error. But if you voted for name error, you’re still technically right because a no method error is a class of name error. Regardless of the specifics, it's valuable because this is a runtime error, not a syntax error. This means we can fix it.
00:11:47.680 To understand how Ruby interprets the code, we note that it does not look like Ruby code at all, but it is plausible Ruby. Ruby assumes we’re trying to call a method named 'var.' This becomes more evident if we insert optional parentheses. Let's start from the back: the optional semicolon and the optional parentheses make the function clearer, where we define a string literal that we then assign to a local variable called 'message.'
00:12:11.440 Then we attempt to call the method 'var' with whatever is inside those parentheses. It turns out that variable assignments in Ruby yield a value. Therefore, the result of the assignment expression in Ruby is the value being assigned, which is on the right-hand side—in this case, the string literal 'Hello World.' Thus, we can rewrite it as 'message = "Hello World"' and then call 'var' with 'message.'
00:12:36.860 So, hopefully that clarifies the situation. As you can see, because Ruby does not require special syntax for defining a local variable, the first line is all we wanted to achieve. So we don’t need any method definitions to execute anything, just that it doesn't throw an error and allows our code to run.
00:12:51.720 If you look at the error message again, Ruby is complaining that there is no method named 'var.' It’s a simple fix—define a method called 'var' that does nothing. If we run that code again, it will work.
00:13:17.800 Now, let’s talk about uninitialized variables in JavaScript. It is possible to declare a variable without initializing it to a value. For instance, 'var message;' doesn't assign any value when you declare the variable. What do you think will happen if we run this code? Is it just going to work, or will it give a syntax error, a no method error, or a name error? Let’s see some hands.
00:13:48.180 Who thinks it’s A? A few hands. Who thinks it’s B? No hands for B. C? A few hands. D? Okay, we have roughly the same number of hands for C and D. The correct answer is a name error! Ruby assumes we're trying to call a method named 'message' because we never defined a local variable before the first line. If we clarify with optional parentheses, we see that we're calling a method, not defining a variable.
00:14:11.680 So, just like before, we can define the 'message' variable without assigning it anything; Ruby will create it when you first assign it. If you look at the error message again, Ruby is saying there’s no method called 'message.' We can define such a method, but that could feel like cheating. What we want is to work with any arbitrarily named variables.
00:14:43.920 Ruby provides a helpful mechanism for unhandled method calls: the 'method_missing' method. Defining a 'method_missing' method that does nothing will capture all unhandled method calls and suppress errors, so our code works again. The first line 'var message' does nothing, yet when we execute 'message = "Hello World," it assigns the value properly and the last line uses the local variable.
00:15:08.600 Success! Next, let's learn how to define functions. This is how to define a function in JavaScript: 'function hello() { }'. When you want to invoke it, you just do 'hello().' This is quite similar to Ruby! What would happen if we try to run this code? Would it just work? Would we encounter a syntax error, a no method error, or a name error? Let’s gather more hints.
00:15:34.700 Who thinks it’s A? B? C? D? This is a trick question—the correct answer is that nothing happens! The code runs fine, but there are no errors, and no output is produced. Now why is that? Let’s see how Ruby interprets the code. It doesn’t look like Ruby code, but upon inspection, Ruby thinks we’re calling a method called 'hello' without passing any positional or keyword arguments. We may pass a block argument to 'hello' as the last part.
00:16:04.320 Let’s rewrite this to clarify. The curly braces indicate we are passing a block to the method, which changes how Ruby interprets it. The method 'function' may not even be defined. How come it works? We’re relying on 'method_missing' to handle any uncreated block.
00:16:29.640 What we can do in this situation is when we want to define a JavaScript function, we should check if a block exists in 'method_missing.' If there is a block, we will define a function call within the context class, thereby giving us a way to define and invoke JavaScript functions directly.
00:16:59.200 Finally, let’s discuss function arguments in JavaScript. Functions become more meaningful when they can take arguments. For example, you can define a function in JavaScript like this: 'function hello(name) { }'. You can use the name argument inside that function. And, of course, when you call it, you pass arguments as you would expect.
00:17:21.600 Now, let's discuss what would happen if we try to run this code. Would it work, throw a syntax error, an argument error, or maybe nothing at all? Who thinks it’s A? No hands. B? C? D? E? We have a variety of guesses here! Well, I’m out of time, so you will have to work it out yourself.
00:17:40.480 There are many more interesting aspects of the gem I would love to show, but I can only fit so much into this half-hour talk. If you find this interesting, please chat with me in the hallway track. The code is available on the internet through GitHub, and there’s a link from the Ruby Gems page as well. The version on GitHub actually does many more things than what I've shown you here, and it is surprisingly robust. I wouldn’t write production code with it, but you can achieve some awesome JavaScript-like functionality in Ruby.
00:20:00.880 Moreover, I have tests and good documentation. I've heard that to encourage people to contribute to your open-source project, you should have tests and good documentation, so I have both! Hopefully, you will consider contributing to this project.
00:20:30.440 Now, at this point, you might be wondering why I've spent this half hour talking about what seems to be completely useless. You’re probably right, but why not have a little fun? As programmers, we often get caught up in building something useful and may overlook just enjoying the process.
00:20:56.480 Most of us started programming because it was fun to build things on the computer. It's essential to preserve that passion over time. This talk stems from ridiculous ideas that emerged on a Saturday, where I'm essentially playing a single-player game of coding ping pong with the Ruby interpreter.
00:21:24.840 During this process, I realized a surprising amount of stuff about both Ruby and JavaScript. For instance, do you know how Ruby distinguishes between local variables and method calls when no parentheses are used? Or what happens when you try to use a return statement within a block?
00:21:40.720 I can even point to specific places in the Rails codebase where this knowledge has aided my understanding. While this knowledge may not seem particularly useful right now, the idea here is that I didn't start out with the goal of learning these intricate details in mind. It all happened organically along the journey.
00:22:32.320 So, I encourage you to take some time with your computers. It doesn’t have to be silly things, but just go build something—something fun for you. Whether it’s a magic card collector app or any other project, I hope you learn something new through these endeavors.
00:22:52.200 And importantly, I hope this helps reignite the passion you have for programming. If nothing else, you can take these topics to conferences and discuss them!
00:23:15.440 That's all I have for today. And remember, if Shen can code, then so can you. Thank you very much!