A Primer on Ruby Yield and Ruby Blocks
Ruby blocks are an interesting feature in Ruby, loved by developers all over the world. They allow you to group code statements, pass them to functions, and write flexible and clean Ruby code. The features and convenience that Ruby blocks offer compared to standard methods in terms of execution and maintenance make them a very strong asset in Ruby programming.
Yield is a special keyword that helps invoke Ruby blocks. But are they just that? Certainly not. In this article, we take a look at how Ruby blocks and Ruby yield help make life easier for Ruby developers. We will also go through other Ruby keywords and concepts that are connected to blocks and understand how everything works together.
Feel free to navigate around the post with these links:
What are Ruby blocks?
In simple words, Ruby code blocks are chunks of code between do…end statements or curly braces, and can be associated with method invocations. A block is a way of grouping code together. It is different from a method as it is expected to be a building block of methods.
It is important to note that a block is always invoked from a method with the same name as that of the block. This invoking of the block happens through the yield keyword, which we’ll dive into, a little later.
What’s an example of a Ruby block?
Let’s clarify Ruby blocks with an example:
def code_block
puts ‘Start of method’
# you can call the block using the yield keyword.
yield
puts ‘End of method’
yield
end
code_block {puts ‘Hit the block!’}
This is what the output looks like:
> ruby main.rb
Start of method
Hit the block!
End of method
Hit the block!
In the above example, note that the code_block method defined in the first line has a block associated with its call in the last line. Also, the block conventionally appears right after a method call. This kind of block is called an implicit block.
How do Ruby blocks Work?
In the last section, we came across a lot of information. Let’s take a look at some properties of blocks, to get some context:
How can Ruby blocks be declared?
Blocks can be declared either by using do … end statements, or by using curly braces {}. As we saw in the previous example, the block was defined as:
code_block {puts ‘Hit the block!’}
This is an example of using curly braces to declare a block. The same block can be defined using do … end in the following way:
code_block do
puts ‘Hit the block!’
end
Are Ruby blocks implicit or explicit?
Blocks can be categorized into either explicit and implicit blocks. All the examples we’ve seen till now have used implicit blocks.
An explicit block is different as it can be saved into a variable and passed into other methods as and when needed. It is quite similar to a first-class function, where functions can be treated just like other variables and be passed around as arguments while retaining the environment variables which it was called in.
Explicit blocks are automatically converted into procs. A proc is an instance of the Proc class, which can hold some code to be executed and be stored as a variable. What’s the difference between an explicit block and a proc then?
Here’s what a sample use-case of explicit blocks looks like:
def code_block(&block)
puts ‘Start of method’
block.call # similar to yield
puts ‘End of method’
end
code_block {puts ‘Hit the block!’}
Notice the last lines in both the code snippets. The first snippet passes in a temporary block to the function call and internally uses it with its reference variable. Observe the ampersand sign (&) before the variable name in the signature. This is how explicit blocks are passed to method signatures in Ruby. If there’s an ampersand sign in the argument of a method, it will expect the passed argument to be a block of code, then convert it into an object, and store it to be later invoked by a yield or .call statement.
Here’s how the same thing can be implemented using Procs:
def code_block_with_proc(proc)
puts ‘Start of method’
proc.call # similar to yield
puts ‘End of method’
end
proc = Proc.new {puts ‘Hit the block!’}
code_block_with_proc(proc)
The above snippet, on the contrary, utilizes Procs and declares the same chunk of code in an object reference by a variable, which is passed to the method call. Invoking the chunk of code is the same in both cases (through var.call ), but the proc snippet does not need an ampersand sign to denote a proc variable— it is treated just like other method signature arguments.
Where should implicit Ruby blocks appear?
Implicit blocks should appear right after their associated method calls in the source code. As obvious as it is by now, blocks are passed into methods as a generic argument, so they should appear right after the method call, just like other arguments.
def code_block
puts ‘Start of method’
yield
end
code_block {puts ‘Hit the block!’} # right after the function
Can Ruby blocks take in arguments?
Yes – since blocks are miniature methods themselves, they’re naturally expected to be dynamic and adapt to changing environments. Block arguments make this possible. Each block can take in arguments, operate on them, and then return a value. Here is a sample block and its method that takes an argument:
def code_block
yield 42
puts “You are in the method”
yield 84
end
code_block {|i| puts “You are in the block and have passed in this: #{i}”}
This results in the following output upon execution:
You are in the block and have passed in this: 42 |
Wrapping around an identifier with pipes (|) on both sides declares it as a method argument. This is how you can utilize blocks as small utility functions. The ability to pass arguments opens up a whole new dimension of possibilities. Here’s how block arguments and each make iteration over arrays super convenient:
[10, 20, 30].each {|n| puts n}
Output:
10
20
30
This is how simple printing the contents of an array can become with the help of blocks.
You can also do something a little more complex, like calculating the sum of all elements in an array:
array = [100, 200, 300]
sum = 0
array.each { |a| sum+=a }
puts sum
This outputs 600 –the expected sum of all elements in the array.
You can build on this example to solve common problems, like finding the extremums in an array, transforming the elements of the array based on some rules (eg. sorting, filtering, morphing, etc.).
Where are BEGIN and END blocks run in Ruby?
BEGIN and END blocks are run at the respective ends of the Ruby source code file. Two special blocks– BEGIN and END, are defined in Ruby to be run on the extreme ends of code execution. The Ruby interpreter automatically runs the BEGIN block in a file before invoking any other code. Similarly, it runs the END block after the last line of code is executed. Here’s how they are declared:
BEGIN {
puts “BEGIN code block”
}
END {
puts “END code block”
}
# MAIN block code
puts “ACTUAL Code”
This produces the following output:
BEGIN code block
ACTUAL Code
END code block
These can be utilized to imitate the behavior of lifecycle methods in your code. Say you want to initialize a global variable before it is used in some method declared later in a file — you can simply define the initializer code in the BEGIN block. References to heavy resources can be freed in the END block to ensure garbage collection.
Can Ruby blocks house locally scoped variables?
Another important feature of blocks that make them very similar to methods is local variable scoping. Variables defined inside a block remain local to the block and are not retained once the block finishes its execution. This enhances code readability as the same variable names can be utilized more than once in different contexts to maintain conventions. Take a look at the following piece of code:
a = 42
def code_block
yield
end
code_block do
a = 84
puts “The current element is #{a}”
end
This will output the following:
The current element is 84
As you can see, here the local variable a (inside the block) is overshadowing the global a. This is how block scopes work in Ruby.
Let’s take a look at another example related to local variable scoping, that brings to light a bug in one of the Ruby versions.
n = 7
(1…3).each {|n| puts n}
puts n
The output for the above snippet will be different in two different versions of Ruby. Apparently, in Ruby 1.8, the output will be:
1
2
3
3
And for Ruby 1.9, it will be:
1
2
3
7
This is due to a bug in Ruby 1.8 which persists the last available data in the local variable to its global copy as well. This is worth mentioning as it can cause a lot of bugs if not handled carefully.
<h3>Can Ruby blocks be declared via Lambdas?
As we just saw, blocks in Ruby 1.8 are not the safest way to go. So, there exists another approach to manage small chunks of code traditionally handled by natively defined blocks.
Lambdas are an alternate method of defining blocks. They get rid of the temporary nature of the blocks, as they don’t run them where they are defined in the source code. Instead, they prepare the block and allow storing the block in a reference variable. Here’s an example of declaring a block via a lambda:
my_block = -> { puts “Code Block!” }
Executing this line would not print anything on the command line. Rather, you need to explicitly define a call to this block, similar to:
my_block.call
This would result in the following output:
Code Block!
Note that there are multiple ways of invoking lambda-declared blocks. Here are some of the other (usually less preferred) ways:
my_block.()
my_block.===
my_block[]
What is Ruby yield?
Yield is a keyword used to invoke a block inside a method. It can take in arguments, and can also return values from blocks. Let’s take a look at how these things happen.
How does the Ruby yield keyword work with blocks?
We caught a glimpse of it in our very first sample block, which was:
def code_block
puts ‘Start of method’
# you can call the block using the yield keyword.
yield
puts ‘End of method’
yield
end
code_block {puts ‘Hit the block!’}
The keyword yield here was used to define exactly where the passed code block will be invoked. This is how blocks work with methods; yield can also take in arguments, as we saw in the example above:
def code_block
yield 42
puts “You are in the method”
yield 84
end
code_block {|i| puts “You are in the block and have passed in this: #{i}”}
As we can see, yield can be used multiple times in a method.
The equivalent of yield in the case of explicit blocks is call. A block reference can be invoked directly by calling call on it.
Yield can also be used to access the return value from a block. Here’s a sample that will make it clearer:
def code_block
A = yield
puts A
end
code_block { “Code Block!” }
This will result in the following output:
Code Block!
What’s happening here is that the string returned by the block is being stored into the variable A inside the method, and is then being printed out. This completes the basic prerequisites for blocks to act as effective functions — they can take in arguments, process them, and return required results.
Another important thing to take care of when using yield or block.call is to ensure that a block is passed to the method call. If not, the interpreter would throw a LocalJumpError.
Here’s an example:
def code_block
yield
end
code_block # no block is passed – LocalJumpError will be thrown here
code_block { puts “Code Block!” }
Ruby provides a utility method called block_given? to check for such cases before invoking the block. Here’s how you can use block_given? To fix the previous snippet:
def code_block
yield if block_given?
end
code_block # does not raise any error
code_block { puts “Code Block!” }
What are the use cases for Ruby blocks?
Enough about theory and dummy code examples! Let’s take a look now at some of the more meaningful, practical use cases of blocks in Ruby.
How do I time my code with Ruby blocks?
More often than not, the need to measure your code’s speed and performance might arise. Naively capturing timestamps for every block of code that you want to gauge can be cumbersome. Here’s a smart way to do it using blocks:
def measure_time do
start = Time.now
yield
Time.now – start
end
measure_time { “some random string” * 10000000 }
The above snippet will return the time that was spent in executing the allocation of a large string in memory. It works by noting the time when the measure_time method was invoked, then running the actual code to be tested via the yield call, and finally calculating the difference between the end time and the start time of the method.
Relatively more complex algorithms can be easily gauged using this architecture. Let’s take insertion sort as an example:
measure_time do
array = [6, 5, 3, 1, 8, 7, 2, 4]
final = []
final << array.shift
for i in array
final_index = 0
while final_index < final.length
if i <= final[final_index]
final.insert(final_index,i)
break
elsif final_index == final.length-1
final.insert(final_index+1,i)
break
end
final_index+=1
end
end
final
end
To make this even simpler, let’s use explicit blocks with lambdas, as we saw earlier. So now, the whole code would look like:
# declare the incoming code block explicitly
def measure_time(&block) do
start = Time.now
block.call
Time.now – start
end
# define the ins_sort method using a lambda
ins_sort = -> do
array = [6, 5, 3, 1, 8, 7, 2, 4]
final = []
final << array.shift
for i in array
final_index = 0
while final_index < final.length
if i <= final[final_index]
final.insert(final_index,i)
break
elsif final_index == final.length-1
final.insert(final_index+1,i)
break
end
final_index+=1
end
end
final
end
# invoking becomes a breeze!
measure_time ins_sort
# similarly declare more testable code via lambdas and invoke them accordingly
# measure_time bub_sort
# measure_time sel_sort
# .
# .
As you can see, this can be quite easily extended to measuring time-based metrics for other algorithms.
How do I use result callbacks in Ruby blocks?
Many chunks in your code might run asynchronously and would be required to return a result to the calling environment. Traditionally this is carried out with the help of callbacks.
Blocks make defining callbacks super smooth! Here’s how you can receive an onsuccess and an onfailure event from a method using a callback:
def some_async_task(&onsuccess, &onfailure) do
result = false # a variable to store the operation’s result
.
. # carry out the async task here and update the result variable accordingly
.
if result onsuccess.call # call the appropriate methods according to the result
else onerror.call
end
# pass the callback while invoking the method
some_async_task { puts “Yay! Success!” } { puts “Whoops! Failure!” }
This simplifies the process of handling callbacks from asynchronous events and makes the syntax similar to other callback-based languages like Javascript.
How do I decorate data with Ruby blocks?
Sometimes, your outputs might require cleaning, formatting, and/or wrapping data around in templates before actually processing them. While this can be done manually, blocks offer an additional level of simplicity here. First, let’s take a look at how you can do a simple HTML-wrap in Ruby.
def wrap_with_p(text)
“<p>#{text}</p>”
end
wrap_with_p(“Hello world!”)
This seems fairly straightforward using only functional logic. Now, let’s think of a use case where the return value from the wrap_with_p method can be further processed in different ways, for example– printing to the screen and saving to the database. To achieve this with methods, you would need to explicitly define the two use cases in different methods, something similar to this:
def wrap_with_p(text)
“<p>#{text}</p>”
end
def save_to_db(data)
# Assume DB to be a hypothetical accessor object to a database
DB.save(wrap_with_p(data))
end
def print_to_screen(data)
puts wrap_with_p(data)
end
payload = “Hello world!”
save_to_db(payload)
print_to_screen(payload)
Now let’s see how this can be made super simple using blocks:
def wrap_with_p(data)
html = “<p>#{data}</p>”
yield html
end
payload = “Hello World!”
# Save to DB with ease
wrap_with_p(payload) { |html| DB.save(html) }
# Print to the screen
wrap_with_p(payload) { |html| puts html }
That’s the level of ease and convenience that Ruby blocks bring to the table! You can work with blocks to create more of such tiny helper methods and use them to write cleaner, more efficient Ruby code.
Quick Recap on Ruby Yield and Blocks
In this article, we began by taking a bird’s eye view of Ruby blocks and the Ruby yield keyword, before diving deeper into both of them. We analyzed different properties of blocks in great detail and understood how they are different from conventional callbacks in most languages, making them much more powerful and customizable. Next, we understood how the yield keyword works together with Ruby blocks and then moved on to look at some practical use cases of blocks.
That was pretty much everything about Ruby blocks and the complementary yield keyword. For more of such in-depth content around web development, and a solid tool for optimizing your application’s performance, feel free to navigate around our website and blog!