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 what10.times
means. 10 is an instance of theFixnum
class, which has atimes
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 thedo
keyword means... that you're passing a block as an argument to a method. - Once YARV starts executing the
times
method, it creates a newrb_control_frame_t
structure. As Nadia said at one point, eachrb_control_frame_t
structure represents a level in your Ruby program’s call stack. So each time you call a method, you get a newrb_control_frame_t
structure and a new level in your Ruby program's call stack. Each time you return from a method, arb_control_frame_t
structure is deleted. - This
rb_control_frame_t
has a type CFUNC because thetimes
method was written by the Ruby team in C and not Ruby. What thetimes
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 anotherrb_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.
@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.