A quickish take on Blocks in Ruby

Alfonso Gonzalez
Jun 28 · 9 min read

I am currently enrolled in the Launch School program as I transition to a career in software development. What follows is based on what I have learned in the curriculum so far, as it pertains to blocks.

Photo by Ryan Quintal on Unsplash

Even though it is said that in Ruby everything is an object, there are a few exceptions. Blocks are one of those exceptions that can be very helpful as they give flexibility and added functionality to our Ruby programs because of how they work. This article aims to explore them a little more in depth.

I will cover the following features of blocks in Ruby:

  • Yield keyword
  • Block parameters
  • Closures
  • Explicitly passing a block to a method
Photo by Will H McMahan on Unsplash

Blocks are created by placing code (expressions and/or statements) between do..end or curly braces {..}. Blocks can be passed into methods implicitly as an argument upon method invocation. On a related and important side note, all methods in Ruby can take an implicit block argument. And by implicit we mean that the block is an optional argument and it is up to the method to either execute the code inside the block or ignore it altogether. One way I like to think of that last statement is that Ruby will not complain that there is a “random” extra argument (in this case the actual block) being passed into the method, upon invocation.

Take for example the following code snippet:

1.times do
puts 'Hi!'
end

Above we have an integer 1calling the times method followed by a do..end block with the expression puts 'Hi!' inside the block. The block is the argument being passed into the times method and the code inside the block puts 'Hi!' gets executed upon invocation of the times method. Yes, that last statement is correct. The piece of code between the do..end keywords is an actual argument that is passed into the times method. Upon invocation, the snippet above will output the string Hi! one time as that is the way the Integer#times method is implemented.

But how exactly does a method know to execute the block and when to ignore it? What sort of magic is happening here? The answer lies in the yield keyword. yield does exactly that, it will yield the execution of the method to the block that was passed into it as an argument when the method was invoked.

Let’s explore that by defining a custom times method that works like Ruby’s built in Integer#times method so that we can visualize what’s going on behind the scenes. Now, in the Ruby documentation we are told that the times method iterates over the given block the same number of times as the calling object. But the counting begins at the number 0 and ends the iteration at the value of 1 minus the calling object. ( times is an instance method from the Integer class so the calling object has to be an integer) We can create our custom times method using a while loop to iterate based on the original method explanation in the Ruby documentation.

def times(number)
counter = 0
while counter < number
yield
counter += 1
end
number
end
times(1) { puts "Hi!" } # => Hi!

The method above works almost the same as the Integer#times which I used in the initial example (The almost part I will explain shortly) As you can see, there is a lot going on inside the method. We have a variablecounter to keep track of the number of iterations and have set its initial value to 0 to duplivate how Integer#times works. We then used a while loop that will check to see if the value of counter is less than the value referenced bynumber. Once that is no longer the case, the loop will be exited and the method returns the original argument referenced by number. This last detail is also another feature of Integer#times. But most importantly for this discussion, inside the while loop we use the yield keyword which yields to the block. This is where the “magic” happens. yield tells Ruby to look for a block and execute the code inside the block. I like to think of this as the method taking a time out and letting the block do its work before continuing its own job. The value that yield returns is the return value of the block which is the last expression in the block.

When we invoke our times method above, we pass in the integer 1 as an argument which is now being referenced by number inside the method body. The block { puts "Hi!" } is an additional implicit argument passed into times. Once the method starts to execute the code, inside the while loop, it sees yield and Ruby says, “Let’s see if there’s a block around here…”, it finds the block argument that was passed intotimes at invocation and executes the code in the the block (in this caseputs "Hi!" ) outputting the string Hi! and voila. Just like magic right?

Let’s call our custom times method one more time to expand on yield some more. Now let’s try calling our times method without an implicit block to see if it is truly an optional argument.

def times(number)
counter = 0
while counter < number
yield
counter += 1
end
number
end
times(1) # => (LocalJumpError) no block given

Ooopss…. We get an error this time telling us that we did not pass in a block. How can that be if the block is supposed to be optional? The answer lies once again in the Ruby documentation and how yield works. The definition states the following:

yield

Called from inside a method body, yields control to the code block (if any) supplied as part of the method call. If no code block has been supplied, calling yield raises an exception.

So adding the keyword yield actually tells Ruby to look for a block and if it does not find one Ruby will complain and let you know. So maybe there’s added magic here that we are not aware of? How does the Integer#times method ignore the invocation if no block argument has been supplied? This is why the custom times method above “almost” works like the Integer#times method. We are missing a key feature.

There is another handy built in method from the Kernel module Kernel#block_given? which we can add as a guard clause and helps the method ignore yield if no block has been provided at invocation. So adding that piece of information makes the final code for our custom times method look like this:

def times(number)
counter = 0
while counter < number
yield if block_given?
counter += 1
end
number
end
times(1) { puts "Hi!" } # => Hi!
p times(1) # => 1

Now our custom times method works just like Ruby’s built in Integer#times

Photo by Roman Synkevych on Unsplash

With blocks we can set parameters (called block parameters) and pass arguments to them through yield. What’s cool (and sometimes tricky) about this is that the blocks don’t really enforce argument counts like normal methods do. Because of this, blocks are said to have lenient arity rules. Let’s create a new custom method to see this in action:

def a_method
yield(1)
end
a_method { |number| puts number } # => 1

In the code above we have set a block parameter |number| which becomes a local variable inside the block passed into puts as an argument. Inside a_method the yield keyword is passed in an argument 1 in between the parenthesis which it will be referenced by number upon execution of the block. This is why the code above outputs 1

But what if we don’t pass in an argument to yield? Will we get an error? Let’s try it:

def a_method
yield
end
a_method { |number| p number } # => nil

Nope, no error here and this is because of the arity of blocks I mentioned earlier. Remember that blocks do not enforce argument counts. What happens instead is that the variables inside the block are assigned a value of nil which is what is output above upon execution. This works the other way around as well. If we passed more arguments to yield than there are block parameters, the code will still work and the extra arguments will simply be ignored:

def a_method
yield(1, 2, 3)
end
a_method { |number| p number } # => 1
Photo by Lina Verovaya on Unsplash

Blocks are a way in which Ruby creates closures. Closures are defined as chunks of code that can be saved, passed around and executed. Perhaps more importantly, they are called closures because they create a binding, or they are said to bind its surrounding artifacts and as such they remember the context in which they were defined. This is handy because it means that the block can be called in a different scope yet still maintain access to all the stuff in the scope where the block was created. The best metaphor I remember reading about this is “to think of a block as having a backpack that contains all of the information surrounding it and therefore it has access to that information wherever it goes (I am paraphrasing from a different article that I have been unable to locate again) An example of this would be:

var = 'Hi!'def a_method
yield
end
a_method { puts var } # => Hi!

Even though var was initialized outside the scope of a_method, the block has access to the value it is referencing because it was created in the same scope as var. Note that if we initialized var after the closure was created, the block will not be able to reference its value:

def a_method
yield
end
a_method { puts var } # => undefined local variable or method 'var'var = 'Hi!'
Photo by Max Anderson on Unsplash

Blocks can also be passed into a method explicitly. We do this by prepending an ampersand & to the last parameter in a method definition like this:

def a_method(&block)
block.call
end
a_method { puts 'Hi!' } # => Hi!

The reason we say the block is being passed in explicitly is because it is now a parameter in between parenthesis in the method definition. So why would we want to pass a block explicitly you ask? Well if we think of blocks as nameless methods then we can see how that could limit where the block can be used since they are not actual objects. By passing a block explicitly we are giving the method a name that can be passed around to be used in other places just like regular objects. Kind of like we do with objects. This gives us added flexibility in our code.

Notice how we now are using the call method in the previous example. Why is that? The reason is that by prepending the ampersand & to the last parameter, this tells Ruby to turn the block into a simple proc object. A proc object is an instance of the Proc class which when created holds a block of code and can be stored in a variable. The call method is from the Proc class as we can no longer just use theyield keyword to yield to the block. But as an added bonus, we can now use the block as an object and pass it around to other methods. We can see this in the following example:

def a_method(block)
p block
end
def another_method(&block)
a_method(block)
end
another_method { "Hi!" } # => <Proc:0x00007fbcd1836658...>

Above we can see that the explicit block passed into another_method as an argument is converted by Ruby into an object of the Proc class as is outputted when we call inspect on block inside a_method. Notice how we can now pass it without the ampersand to a_method and use it in there as a regular object. This example is only showing us that it is indeed aproc object. If we want to execute the code inside the block stored in the proc we need to use the Proc#call method as such:

def a_method(block)
puts block.call
end
def another_method(&block)
a_method(block)
end
another_method { "Hi!" } # => Hi!

And that’s it! As you can see, blocks give us added flexibility through all their features. They help make code more reusable by letting us implement general methods that can be fine tuned at invocation by creating more specific code inside blocks that get passed into methods.

I hope you have found this article helpful and good luck in your coding journey!

Launch School

Publications of the Launch School Community