Skip to content

Instantly share code, notes, and snippets.

@patshaughnessy
Last active August 18, 2017 19:38
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save patshaughnessy/ace3616fcdbf13e70abf8d073707e9e7 to your computer and use it in GitHub Desktop.
Save patshaughnessy/ace3616fcdbf13e70abf8d073707e9e7 to your computer and use it in GitHub Desktop.
Ruby Book Club Questions - Episodes 7 and 8

Hi guys, So in episode 7 you were asking about where and how the puts method connects to the computer’s display and writes the output. And in episode 8 you were asking about the times method, how that worked and why it wasn’t implemented with YARV instructions. In both cases, I didn’t go into detail about this in the book because it would have distracted you from the topic at hand, which is how YARV executes your code. (Or in this case, my example Ruby code from the book.)

Both the puts and times methods were written by the Ruby code team, and not by you or me. Both of them are implemented in C code. So when you’re writing a Ruby program, some of the methods you write yourself, but you get many other methods for free because they are part of the Ruby language. Many of the these built in methods are part of the standard library, which means they are Ruby code written by the Ruby core team, while other built in methods are written directly in C code by the Ruby team.

As you know, the puts method takes a string argument and writes it to the console, returning the value nil. This actual work is performed by C code inside of Ruby which I don't show in the book. The YARV instructions, which your Ruby is converted into, simply calls this C code at the proper time. This is what the send YARV instruction does. send means to call a method. Later in Chapter 4 I get into the details of what the send instruction actually does, and what it really means to call a method. So the YARV instructions don't write anything to the display; the C code for the puts method does that.

To address your questions in episode 8 about how the times method works, and what the rb_control_frame_t structures are, let’s walk through this example again:

10.times do
  puts "The quick brown fox jumps over the lazy dog."
end 
  • So first, we start with the number 10.
  • Then, we call the times method on it. That’s what 10.times means. 10 is an instance of the Fixnum class, which has a times method. I’ll get into classes and objects in chapter 5.
  • The times method takes a single block as a parameter. In Ruby methods can take many normal arguments, and optionally they can take one block as an argument as well. That's what the do keyword means... that you're passing a block as an argument to a method.
  • Once YARV starts executing the times method, it creates a new rb_control_frame_t structure. As Nadia said at one point, each rb_control_frame_t structure represents a level in your Ruby program’s call stack. So each time you call a method, you get a new rb_control_frame_t structure and a new level in your Ruby program's call stack. Each time you return from a method, a rb_control_frame_t structure is deleted.
  • This rb_control_frame_t has a type CFUNC because the times method was written by the Ruby team in C and not Ruby. What the times method does is loop from 0 up to the given number minus one (so from zero to nine inclusive), calling the block each time.
  • Now the times method calls the block, and so we get yet another rb_control_frame_t structure. This one has a type BLOCK because it’s a block.
  • Now the block code runs, which is comprised of YARV instructions, because I wrote it (the “puts the quick brown fox…” line).

If you’re brave and also curious, you can see the C code for the times method here:

https://github.com/ruby/ruby/blob/trunk/numeric.c#L4972

Basically, this is the C code equivalent of this Ruby code:

for n in 0..9 do
  yield n
end 

It loops from 0 to 9 (because I called times on the object 10), and calls yield n each time around the loop. yield calls the given block (the block passed into the current method as an argument) and passes the given arguments to the block.

(It's a bit more complicated that that, because if you don't pass in a block times returns an enumerator instead. Also the C code has a special case when the receiver is not a Fixnum object.)

A block is really just a special kind of method - one that, as you'll see later in Chapter 3, can access values in the parent or calling scope.

@enebo
Copy link

enebo commented Jul 17, 2017

@patshaughnessy that 'for' is the analogue to the C code but I always like to point out the semantic difference between block APIs like 'times' and a 'for' loop in Ruby. Namely, the for loop does not construct a new rb_control_frame_t and merely uses the existing frame.

For people who have seen goofy benchmarks which uses 'for' or 'while' ... it is not because they are goofy programmers but they are trying to eliminate the frame costs to not cloud the benchmark.

@patshaughnessy
Copy link
Author

Right! So what I said above, that "do" means you are passing a block as an argument, doesn't apply in for-loops or while-loops. One confusing detail about Ruby syntax I suppose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment